camcom 1.0.3
 
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.39.0"
12#define CPPHTTPLIB_VERSION_NUM "0x002700"
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/*
209 * Headers
210 */
211
212#ifdef _WIN32
213#ifndef _CRT_SECURE_NO_WARNINGS
214#define _CRT_SECURE_NO_WARNINGS
215#endif //_CRT_SECURE_NO_WARNINGS
216
217#ifndef _CRT_NONSTDC_NO_DEPRECATE
218#define _CRT_NONSTDC_NO_DEPRECATE
219#endif //_CRT_NONSTDC_NO_DEPRECATE
220
221#if defined(_MSC_VER)
222#if _MSC_VER < 1900
223#error Sorry, Visual Studio versions prior to 2015 are not supported
224#endif
225
226#pragma comment(lib, "ws2_32.lib")
227
228#ifndef _SSIZE_T_DEFINED
229using ssize_t = __int64;
230#define _SSIZE_T_DEFINED
231#endif
232#endif // _MSC_VER
233
234#ifndef S_ISREG
235#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
236#endif // S_ISREG
237
238#ifndef S_ISDIR
239#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
240#endif // S_ISDIR
241
242#ifndef NOMINMAX
243#define NOMINMAX
244#endif // NOMINMAX
245
246#include <io.h>
247#include <winsock2.h>
248#include <ws2tcpip.h>
249
250#if defined(__has_include)
251#if __has_include(<afunix.h>)
252// afunix.h uses types declared in winsock2.h, so has to be included after it.
253#include <afunix.h>
254#define CPPHTTPLIB_HAVE_AFUNIX_H 1
255#endif
256#endif
257
258#ifndef WSA_FLAG_NO_HANDLE_INHERIT
259#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
260#endif
261
262using nfds_t = unsigned long;
263using socket_t = SOCKET;
264using socklen_t = int;
265
266#else // not _WIN32
267
268#include <arpa/inet.h>
269#if !defined(_AIX) && !defined(__MVS__)
270#include <ifaddrs.h>
271#endif
272#ifdef __MVS__
273#include <strings.h>
274#ifndef NI_MAXHOST
275#define NI_MAXHOST 1025
276#endif
277#endif
278#include <net/if.h>
279#include <netdb.h>
280#include <netinet/in.h>
281#ifdef __linux__
282#include <resolv.h>
283#undef _res // Undefine _res macro to avoid conflicts with user code (#2278)
284#endif
285#include <csignal>
286#include <netinet/tcp.h>
287#include <poll.h>
288#include <pthread.h>
289#include <sys/mman.h>
290#include <sys/socket.h>
291#include <sys/un.h>
292#include <unistd.h>
293
294using socket_t = int;
295#ifndef INVALID_SOCKET
296#define INVALID_SOCKET (-1)
297#endif
298#endif //_WIN32
299
300#if defined(__APPLE__)
301#include <TargetConditionals.h>
302#endif
303
304#include <algorithm>
305#include <array>
306#include <atomic>
307#include <cassert>
308#include <cctype>
309#include <chrono>
310#include <climits>
311#include <condition_variable>
312#include <cstdlib>
313#include <cstring>
314#include <errno.h>
315#include <exception>
316#include <fcntl.h>
317#include <fstream>
318#include <functional>
319#include <iomanip>
320#include <iostream>
321#include <list>
322#include <map>
323#include <memory>
324#include <mutex>
325#include <random>
326#include <regex>
327#include <set>
328#include <sstream>
329#include <string>
330#include <sys/stat.h>
331#include <system_error>
332#include <thread>
333#include <unordered_map>
334#include <unordered_set>
335#include <utility>
336#if __cplusplus >= 201703L
337#include <any>
338#endif
339
340// On macOS with a TLS backend, enable Keychain root certificates by default
341// unless the user explicitly opts out.
342#if defined(__APPLE__) && \
343 !defined(CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES) && \
344 (defined(CPPHTTPLIB_OPENSSL_SUPPORT) || \
345 defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || \
346 defined(CPPHTTPLIB_WOLFSSL_SUPPORT))
347#ifndef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
348#define CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
349#endif
350#endif
351
352// On Windows, enable Schannel certificate verification by default
353// unless the user explicitly opts out.
354#if defined(_WIN32) && \
355 !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
356#define CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
357#endif
358
359#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) || \
360 defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
361#if TARGET_OS_MAC
362#include <CFNetwork/CFHost.h>
363#include <CoreFoundation/CoreFoundation.h>
364#endif
365#endif
366
367#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
368#ifdef _WIN32
369#include <wincrypt.h>
370
371// these are defined in wincrypt.h and it breaks compilation if BoringSSL is
372// used
373#undef X509_NAME
374#undef X509_CERT_PAIR
375#undef X509_EXTENSIONS
376#undef PKCS7_SIGNER_INFO
377
378#ifdef _MSC_VER
379#pragma comment(lib, "crypt32.lib")
380#endif
381#endif // _WIN32
382
383#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
384#if TARGET_OS_MAC
385#include <Security/Security.h>
386#endif
387#endif
388
389#include <openssl/err.h>
390#include <openssl/evp.h>
391#include <openssl/ssl.h>
392#include <openssl/x509v3.h>
393
394#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
395#include <openssl/applink.c>
396#endif
397
398#include <iostream>
399#include <sstream>
400
401#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
402#if OPENSSL_VERSION_NUMBER < 0x1010107f
403#error Please use OpenSSL or a current version of BoringSSL
404#endif
405#define SSL_get1_peer_certificate SSL_get_peer_certificate
406#elif OPENSSL_VERSION_NUMBER < 0x30000000L
407#error Sorry, OpenSSL versions prior to 3.0.0 are not supported
408#endif
409
410#endif // CPPHTTPLIB_OPENSSL_SUPPORT
411
412#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
413#include <mbedtls/ctr_drbg.h>
414#include <mbedtls/entropy.h>
415#include <mbedtls/error.h>
416#include <mbedtls/md5.h>
417#include <mbedtls/net_sockets.h>
418#include <mbedtls/oid.h>
419#include <mbedtls/pk.h>
420#include <mbedtls/sha1.h>
421#include <mbedtls/sha256.h>
422#include <mbedtls/sha512.h>
423#include <mbedtls/ssl.h>
424#include <mbedtls/x509_crt.h>
425#ifdef _WIN32
426#include <wincrypt.h>
427#ifdef _MSC_VER
428#pragma comment(lib, "crypt32.lib")
429#endif
430#endif // _WIN32
431#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
432#if TARGET_OS_MAC
433#include <Security/Security.h>
434#endif
435#endif
436
437// Mbed TLS 3.x API compatibility
438#if MBEDTLS_VERSION_MAJOR >= 3
439#define CPPHTTPLIB_MBEDTLS_V3
440#endif
441
442#endif // CPPHTTPLIB_MBEDTLS_SUPPORT
443
444#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
445#include <wolfssl/options.h>
446
447#include <wolfssl/openssl/x509v3.h>
448
449// Fallback definitions for older wolfSSL versions (e.g., 5.6.6)
450#ifndef WOLFSSL_GEN_EMAIL
451#define WOLFSSL_GEN_EMAIL 1
452#endif
453#ifndef WOLFSSL_GEN_DNS
454#define WOLFSSL_GEN_DNS 2
455#endif
456#ifndef WOLFSSL_GEN_URI
457#define WOLFSSL_GEN_URI 6
458#endif
459#ifndef WOLFSSL_GEN_IPADD
460#define WOLFSSL_GEN_IPADD 7
461#endif
462
463#include <wolfssl/ssl.h>
464#include <wolfssl/wolfcrypt/hash.h>
465#include <wolfssl/wolfcrypt/md5.h>
466#include <wolfssl/wolfcrypt/sha256.h>
467#include <wolfssl/wolfcrypt/sha512.h>
468#ifdef _WIN32
469#include <wincrypt.h>
470#ifdef _MSC_VER
471#pragma comment(lib, "crypt32.lib")
472#endif
473#endif // _WIN32
474#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
475#if TARGET_OS_MAC
476#include <Security/Security.h>
477#endif
478#endif
479#endif // CPPHTTPLIB_WOLFSSL_SUPPORT
480
481// Define CPPHTTPLIB_SSL_ENABLED if any SSL backend is available
482#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) || \
483 defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
484#define CPPHTTPLIB_SSL_ENABLED
485#endif
486
487#ifdef CPPHTTPLIB_ZLIB_SUPPORT
488#include <zlib.h>
489#endif
490
491#ifdef CPPHTTPLIB_BROTLI_SUPPORT
492#include <brotli/decode.h>
493#include <brotli/encode.h>
494#endif
495
496#ifdef CPPHTTPLIB_ZSTD_SUPPORT
497#include <zstd.h>
498#endif
499
500/*
501 * Declaration
502 */
503namespace httplib {
504
505namespace ws {
506class WebSocket;
507} // namespace ws
508
509namespace detail {
510
511/*
512 * Backport std::make_unique from C++14.
513 *
514 * NOTE: This code came up with the following stackoverflow post:
515 * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique
516 *
517 */
518
519template <class T, class... Args>
520typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
521make_unique(Args &&...args) {
522 return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
523}
524
525template <class T>
526typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
527make_unique(std::size_t n) {
528 typedef typename std::remove_extent<T>::type RT;
529 return std::unique_ptr<T>(new RT[n]);
530}
531
532namespace case_ignore {
533
534inline unsigned char to_lower(int c) {
535 const static unsigned char table[256] = {
536 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
537 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
538 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
539 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
540 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
541 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
542 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
543 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
544 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
545 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
546 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
547 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
548 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226,
549 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
550 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224,
551 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
552 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
553 255,
554 };
555 return table[(unsigned char)(char)c];
556}
557
558inline std::string to_lower(const std::string &s) {
559 std::string result = s;
560 std::transform(
561 result.begin(), result.end(), result.begin(),
562 [](unsigned char c) { return static_cast<char>(to_lower(c)); });
563 return result;
564}
565
566inline bool equal(const std::string &a, const std::string &b) {
567 return a.size() == b.size() &&
568 std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) {
569 return to_lower(ca) == to_lower(cb);
570 });
571}
572
573struct equal_to {
574 bool operator()(const std::string &a, const std::string &b) const {
575 return equal(a, b);
576 }
577};
578
579struct hash {
580 size_t operator()(const std::string &key) const {
581 return hash_core(key.data(), key.size(), 0);
582 }
583
584 size_t hash_core(const char *s, size_t l, size_t h) const {
585 return (l == 0) ? h
586 : hash_core(s + 1, l - 1,
587 // Unsets the 6 high bits of h, therefore no
588 // overflow happens
589 (((std::numeric_limits<size_t>::max)() >> 6) &
590 h * 33) ^
591 static_cast<unsigned char>(to_lower(*s)));
592 }
593};
594
595template <typename T>
596using unordered_set = std::unordered_set<T, detail::case_ignore::hash,
598
599} // namespace case_ignore
600
601// This is based on
602// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
603
605 explicit scope_exit(std::function<void(void)> &&f)
606 : exit_function(std::move(f)), execute_on_destruction{true} {}
607
608 scope_exit(scope_exit &&rhs) noexcept
609 : exit_function(std::move(rhs.exit_function)),
610 execute_on_destruction{rhs.execute_on_destruction} {
611 rhs.release();
612 }
613
615 if (execute_on_destruction) { this->exit_function(); }
616 }
617
618 void release() { this->execute_on_destruction = false; }
619
620private:
621 scope_exit(const scope_exit &) = delete;
622 void operator=(const scope_exit &) = delete;
623 scope_exit &operator=(scope_exit &&) = delete;
624
625 std::function<void(void)> exit_function;
626 bool execute_on_destruction;
627};
628
629// Simple from_chars implementation for integer and double types (C++17
630// substitute)
631template <typename T> struct from_chars_result {
632 const char *ptr;
633 std::errc ec;
634};
635
636template <typename T>
637inline from_chars_result<T> from_chars(const char *first, const char *last,
638 T &value, int base = 10) {
639 value = 0;
640 const char *p = first;
641 bool negative = false;
642
643 if (p != last && *p == '-') {
644 negative = true;
645 ++p;
646 }
647 if (p == last) { return {first, std::errc::invalid_argument}; }
648
649 T result = 0;
650 for (; p != last; ++p) {
651 char c = *p;
652 int digit = -1;
653 if ('0' <= c && c <= '9') {
654 digit = c - '0';
655 } else if ('a' <= c && c <= 'z') {
656 digit = c - 'a' + 10;
657 } else if ('A' <= c && c <= 'Z') {
658 digit = c - 'A' + 10;
659 } else {
660 break;
661 }
662
663 if (digit < 0 || digit >= base) { break; }
664 if (result > ((std::numeric_limits<T>::max)() - digit) / base) {
665 return {p, std::errc::result_out_of_range};
666 }
667 result = result * base + digit;
668 }
669
670 if (p == first || (negative && p == first + 1)) {
671 return {first, std::errc::invalid_argument};
672 }
673
674 value = negative ? -result : result;
675 return {p, std::errc{}};
676}
677
678// from_chars for double (simple wrapper for strtod)
679inline from_chars_result<double> from_chars(const char *first, const char *last,
680 double &value) {
681 std::string s(first, last);
682 char *endptr = nullptr;
683 errno = 0;
684 value = std::strtod(s.c_str(), &endptr);
685 if (endptr == s.c_str()) { return {first, std::errc::invalid_argument}; }
686 if (errno == ERANGE) {
687 return {first + (endptr - s.c_str()), std::errc::result_out_of_range};
688 }
689 return {first + (endptr - s.c_str()), std::errc{}};
690}
691
692inline bool parse_port(const char *s, size_t len, int &port) {
693 int val = 0;
694 auto r = from_chars(s, s + len, val);
695 if (r.ec != std::errc{} || val < 1 || val > 65535) { return false; }
696 port = val;
697 return true;
698}
699
700inline bool parse_port(const std::string &s, int &port) {
701 return parse_port(s.data(), s.size(), port);
702}
703
704} // namespace detail
705
707 // no decision has been made, use the built-in certificate verifier
709 // connection certificate is verified and accepted
711 // connection certificate was processed but is rejected
713};
714
716 // Information responses
721
722 // Successful responses
723 OK_200 = 200,
733
734 // Redirection messages
744
745 // Client error responses
756 Gone_410 = 410,
775
776 // Server error responses
788};
789
790using Headers =
791 std::unordered_multimap<std::string, std::string, detail::case_ignore::hash,
793
794using Params = std::multimap<std::string, std::string>;
795using Match = std::smatch;
796
797using DownloadProgress = std::function<bool(size_t current, size_t total)>;
798using UploadProgress = std::function<bool(size_t current, size_t total)>;
799
800// ----------------------------------------------------------------------------
801// httplib::any — type-erased value container (C++11 compatible)
802// On C++17+ builds, thin wrappers around std::any are provided.
803// ----------------------------------------------------------------------------
804
805#if __cplusplus >= 201703L
806
807using any = std::any;
808using bad_any_cast = std::bad_any_cast;
809
810template <typename T> T any_cast(const any &a) { return std::any_cast<T>(a); }
811template <typename T> T any_cast(any &a) { return std::any_cast<T>(a); }
812template <typename T> T any_cast(any &&a) {
813 return std::any_cast<T>(std::move(a));
814}
815template <typename T> const T *any_cast(const any *a) noexcept {
816 return std::any_cast<T>(a);
817}
818template <typename T> T *any_cast(any *a) noexcept {
819 return std::any_cast<T>(a);
820}
821
822#else // C++11/14 implementation
823
824class bad_any_cast : public std::bad_cast {
825public:
826 const char *what() const noexcept override { return "bad any_cast"; }
827};
828
829namespace detail {
830
831using any_type_id = const void *;
832
833// Returns a unique per-type ID without RTTI.
834// The static address is stable across TUs because function templates are
835// implicitly inline and the ODR merges their statics into one.
836template <typename T> any_type_id any_typeid() noexcept {
837 static const char id = 0;
838 return &id;
839}
840
842 virtual ~any_storage() = default;
843 virtual std::unique_ptr<any_storage> clone() const = 0;
844 virtual any_type_id type_id() const noexcept = 0;
845};
846
847template <typename T> struct any_value final : any_storage {
849 template <typename U> explicit any_value(U &&v) : value(std::forward<U>(v)) {}
850 std::unique_ptr<any_storage> clone() const override {
851 return std::unique_ptr<any_storage>(new any_value<T>(value));
852 }
853 any_type_id type_id() const noexcept override { return any_typeid<T>(); }
854};
855
856} // namespace detail
857
858class any {
859 std::unique_ptr<detail::any_storage> storage_;
860
861public:
862 any() noexcept = default;
863 any(const any &o) : storage_(o.storage_ ? o.storage_->clone() : nullptr) {}
864 any(any &&) noexcept = default;
865 any &operator=(const any &o) {
866 storage_ = o.storage_ ? o.storage_->clone() : nullptr;
867 return *this;
868 }
869 any &operator=(any &&) noexcept = default;
870
871 template <
872 typename T, typename D = typename std::decay<T>::type,
873 typename std::enable_if<!std::is_same<D, any>::value, int>::type = 0>
874 any(T &&v) : storage_(new detail::any_value<D>(std::forward<T>(v))) {}
875
876 template <
877 typename T, typename D = typename std::decay<T>::type,
878 typename std::enable_if<!std::is_same<D, any>::value, int>::type = 0>
879 any &operator=(T &&v) {
880 storage_.reset(new detail::any_value<D>(std::forward<T>(v)));
881 return *this;
882 }
883
884 bool has_value() const noexcept { return storage_ != nullptr; }
885 void reset() noexcept { storage_.reset(); }
886
887 template <typename T> friend T *any_cast(any *a) noexcept;
888 template <typename T> friend const T *any_cast(const any *a) noexcept;
889};
890
891template <typename T> T *any_cast(any *a) noexcept {
892 if (!a || !a->storage_) { return nullptr; }
893 if (a->storage_->type_id() != detail::any_typeid<T>()) { return nullptr; }
894 return &static_cast<detail::any_value<T> *>(a->storage_.get())->value;
895}
896
897template <typename T> const T *any_cast(const any *a) noexcept {
898 if (!a || !a->storage_) { return nullptr; }
899 if (a->storage_->type_id() != detail::any_typeid<T>()) { return nullptr; }
900 return &static_cast<const detail::any_value<T> *>(a->storage_.get())->value;
901}
902
903template <typename T> T any_cast(const any &a) {
904 using U =
905 typename std::remove_cv<typename std::remove_reference<T>::type>::type;
906 const U *p = any_cast<U>(&a);
907#ifndef CPPHTTPLIB_NO_EXCEPTIONS
908 if (!p) { throw bad_any_cast{}; }
909#else
910 if (!p) { std::abort(); }
911#endif
912 return static_cast<T>(*p);
913}
914
915template <typename T> T any_cast(any &a) {
916 using U =
917 typename std::remove_cv<typename std::remove_reference<T>::type>::type;
918 U *p = any_cast<U>(&a);
919#ifndef CPPHTTPLIB_NO_EXCEPTIONS
920 if (!p) { throw bad_any_cast{}; }
921#else
922 if (!p) { std::abort(); }
923#endif
924 return static_cast<T>(*p);
925}
926
927template <typename T> T any_cast(any &&a) {
928 using U =
929 typename std::remove_cv<typename std::remove_reference<T>::type>::type;
930 U *p = any_cast<U>(&a);
931#ifndef CPPHTTPLIB_NO_EXCEPTIONS
932 if (!p) { throw bad_any_cast{}; }
933#else
934 if (!p) { std::abort(); }
935#endif
936 return static_cast<T>(std::move(*p));
937}
938
939#endif // __cplusplus >= 201703L
940
941struct Response;
942using ResponseHandler = std::function<bool(const Response &response)>;
943
944struct FormData {
945 std::string name;
946 std::string content;
947 std::string filename;
948 std::string content_type;
950};
951
952struct FormField {
953 std::string name;
954 std::string content;
956};
957using FormFields = std::multimap<std::string, FormField>;
958
959using FormFiles = std::multimap<std::string, FormData>;
960
962 FormFields fields; // Text fields from multipart
963 FormFiles files; // Files from multipart
964
965 // Text field access
966 std::string get_field(const std::string &key, size_t id = 0) const;
967 std::vector<std::string> get_fields(const std::string &key) const;
968 bool has_field(const std::string &key) const;
969 size_t get_field_count(const std::string &key) const;
970
971 // File access
972 FormData get_file(const std::string &key, size_t id = 0) const;
973 std::vector<FormData> get_files(const std::string &key) const;
974 bool has_file(const std::string &key) const;
975 size_t get_file_count(const std::string &key) const;
976};
977
979 std::string name;
980 std::string content;
981 std::string filename;
982 std::string content_type;
983};
984using UploadFormDataItems = std::vector<UploadFormData>;
985
986class DataSink {
987public:
988 DataSink() : os(&sb_), sb_(*this) {}
989
990 DataSink(const DataSink &) = delete;
991 DataSink &operator=(const DataSink &) = delete;
992 DataSink(DataSink &&) = delete;
994
995 std::function<bool(const char *data, size_t data_len)> write;
996 std::function<bool()> is_writable;
997 std::function<void()> done;
998 std::function<void(const Headers &trailer)> done_with_trailer;
999 std::ostream os;
1000
1001private:
1002 class data_sink_streambuf final : public std::streambuf {
1003 public:
1004 explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
1005
1006 protected:
1007 std::streamsize xsputn(const char *s, std::streamsize n) override {
1008 if (sink_.write(s, static_cast<size_t>(n))) { return n; }
1009 return 0;
1010 }
1011
1012 private:
1013 DataSink &sink_;
1014 };
1015
1016 data_sink_streambuf sb_;
1017};
1018
1020 std::function<bool(size_t offset, size_t length, DataSink &sink)>;
1021
1023 std::function<bool(size_t offset, DataSink &sink)>;
1024
1025using ContentProviderResourceReleaser = std::function<void(bool success)>;
1026
1033using FormDataProviderItems = std::vector<FormDataProvider>;
1034
1035inline FormDataProvider
1036make_file_provider(const std::string &name, const std::string &filepath,
1037 const std::string &filename = std::string(),
1038 const std::string &content_type = std::string()) {
1039 FormDataProvider fdp;
1040 fdp.name = name;
1041 fdp.filename = filename.empty() ? filepath : filename;
1042 fdp.content_type = content_type;
1043 fdp.provider = [filepath](size_t offset, DataSink &sink) -> bool {
1044 std::ifstream f(filepath, std::ios::binary);
1045 if (!f) { return false; }
1046 if (offset > 0) {
1047 f.seekg(static_cast<std::streamoff>(offset));
1048 if (!f.good()) {
1049 sink.done();
1050 return true;
1051 }
1052 }
1053 char buf[8192];
1054 f.read(buf, sizeof(buf));
1055 auto n = static_cast<size_t>(f.gcount());
1056 if (n > 0) { return sink.write(buf, n); }
1057 sink.done(); // EOF
1058 return true;
1059 };
1060 return fdp;
1061}
1062
1063inline std::pair<size_t, ContentProvider>
1064make_file_body(const std::string &filepath) {
1065 size_t size = 0;
1066 {
1067 std::ifstream f(filepath, std::ios::binary | std::ios::ate);
1068 if (!f) { return {0, ContentProvider{}}; }
1069 size = static_cast<size_t>(f.tellg());
1070 }
1071
1072 ContentProvider provider = [filepath](size_t offset, size_t length,
1073 DataSink &sink) -> bool {
1074 std::ifstream f(filepath, std::ios::binary);
1075 if (!f) { return false; }
1076 f.seekg(static_cast<std::streamoff>(offset));
1077 if (!f.good()) { return false; }
1078 char buf[8192];
1079 while (length > 0) {
1080 auto to_read = (std::min)(sizeof(buf), length);
1081 f.read(buf, static_cast<std::streamsize>(to_read));
1082 auto n = static_cast<size_t>(f.gcount());
1083 if (n == 0) { break; }
1084 if (!sink.write(buf, n)) { return false; }
1085 length -= n;
1086 }
1087 return true;
1088 };
1089 return {size, std::move(provider)};
1090}
1091
1092using ContentReceiverWithProgress = std::function<bool(
1093 const char *data, size_t data_length, size_t offset, size_t total_length)>;
1094
1096 std::function<bool(const char *data, size_t data_length)>;
1097
1098using FormDataHeader = std::function<bool(const FormData &file)>;
1099
1101public:
1102 using Reader = std::function<bool(ContentReceiver receiver)>;
1104 std::function<bool(FormDataHeader header, ContentReceiver receiver)>;
1105
1106 ContentReader(Reader reader, FormDataReader multipart_reader)
1107 : reader_(std::move(reader)),
1108 formdata_reader_(std::move(multipart_reader)) {}
1109
1110 bool operator()(FormDataHeader header, ContentReceiver receiver) const {
1111 return formdata_reader_(std::move(header), std::move(receiver));
1112 }
1113
1114 bool operator()(ContentReceiver receiver) const {
1115 return reader_(std::move(receiver));
1116 }
1117
1120};
1121
1122using Range = std::pair<ssize_t, ssize_t>;
1123using Ranges = std::vector<Range>;
1124
1125#ifdef CPPHTTPLIB_SSL_ENABLED
1126// TLS abstraction layer - public type definitions and API
1127namespace tls {
1128
1129// Opaque handles (defined as void* for abstraction)
1130using ctx_t = void *;
1131using session_t = void *;
1132using const_session_t = const void *; // For read-only session access
1133using cert_t = void *;
1134using ca_store_t = void *;
1135
1136// TLS versions
1137enum class Version {
1138 TLS1_2 = 0x0303,
1139 TLS1_3 = 0x0304,
1140};
1141
1142// Subject Alternative Names (SAN) entry types
1143enum class SanType { DNS, IP, EMAIL, URI, OTHER };
1144
1145// SAN entry structure
1146struct SanEntry {
1147 SanType type;
1148 std::string value;
1149};
1150
1151// Verification context for certificate verification callback
1152struct VerifyContext {
1153 session_t session; // TLS session handle
1154 cert_t cert; // Current certificate being verified
1155 int depth; // Certificate chain depth (0 = leaf)
1156 bool preverify_ok; // OpenSSL/Mbed TLS pre-verification result
1157 long error_code; // Backend-specific error code (0 = no error)
1158 const char *error_string; // Human-readable error description
1159
1160 // Certificate introspection methods
1161 std::string subject_cn() const;
1162 std::string issuer_name() const;
1163 bool check_hostname(const char *hostname) const;
1164 std::vector<SanEntry> sans() const;
1165 bool validity(time_t &not_before, time_t &not_after) const;
1166 std::string serial() const;
1167};
1168
1169using VerifyCallback = std::function<bool(const VerifyContext &ctx)>;
1170
1171// TlsError codes for TLS operations (backend-independent)
1172enum class ErrorCode : int {
1173 Success = 0,
1174 WantRead, // Non-blocking: need to wait for read
1175 WantWrite, // Non-blocking: need to wait for write
1176 PeerClosed, // Peer closed the connection
1177 Fatal, // Unrecoverable error
1178 SyscallError, // System call error (check sys_errno)
1179 CertVerifyFailed, // Certificate verification failed
1180 HostnameMismatch, // Hostname verification failed
1181};
1182
1183// TLS error information
1184struct TlsError {
1185 ErrorCode code = ErrorCode::Fatal;
1186 uint64_t backend_code = 0; // OpenSSL: ERR_get_error(), mbedTLS: return value
1187 int sys_errno = 0; // errno when SyscallError
1188
1189 // Convert verification error code to human-readable string
1190 static std::string verify_error_to_string(long error_code);
1191};
1192
1193// RAII wrapper for peer certificate
1194class PeerCert {
1195public:
1196 PeerCert();
1197 PeerCert(PeerCert &&other) noexcept;
1198 PeerCert &operator=(PeerCert &&other) noexcept;
1199 ~PeerCert();
1200
1201 PeerCert(const PeerCert &) = delete;
1202 PeerCert &operator=(const PeerCert &) = delete;
1203
1204 explicit operator bool() const;
1205 std::string subject_cn() const;
1206 std::string issuer_name() const;
1207 bool check_hostname(const char *hostname) const;
1208 std::vector<SanEntry> sans() const;
1209 bool validity(time_t &not_before, time_t &not_after) const;
1210 std::string serial() const;
1211
1212private:
1213 explicit PeerCert(cert_t cert);
1214 cert_t cert_ = nullptr;
1215 friend PeerCert get_peer_cert_from_session(const_session_t session);
1216};
1217
1218// Callback for TLS context setup (used by SSLServer constructor)
1219using ContextSetupCallback = std::function<bool(ctx_t ctx)>;
1220
1221} // namespace tls
1222#endif
1223
1224struct Request {
1225 std::string method;
1226 std::string path;
1227 std::string matched_route;
1231 std::string body;
1232
1233 std::string remote_addr;
1234 int remote_port = -1;
1235 std::string local_addr;
1236 int local_port = -1;
1237
1238 // for server
1239 std::string version;
1240 std::string target;
1244 std::unordered_map<std::string, std::string> path_params;
1245 std::function<bool()> is_connection_closed = []() { return true; };
1246
1247 // for client
1248 std::vector<std::string> accept_content_types;
1253
1254 bool has_header(const std::string &key) const;
1255 std::string get_header_value(const std::string &key, const char *def = "",
1256 size_t id = 0) const;
1257 size_t get_header_value_u64(const std::string &key, size_t def = 0,
1258 size_t id = 0) const;
1259 size_t get_header_value_count(const std::string &key) const;
1260 void set_header(const std::string &key, const std::string &val);
1261
1262 bool has_trailer(const std::string &key) const;
1263 std::string get_trailer_value(const std::string &key, size_t id = 0) const;
1264 size_t get_trailer_value_count(const std::string &key) const;
1265
1266 bool has_param(const std::string &key) const;
1267 std::string get_param_value(const std::string &key, size_t id = 0) const;
1268 size_t get_param_value_count(const std::string &key) const;
1269
1270 bool is_multipart_form_data() const;
1271
1272 // private members...
1278 std::chrono::time_point<std::chrono::steady_clock> start_time_ =
1279 (std::chrono::steady_clock::time_point::min)();
1280
1281#ifdef CPPHTTPLIB_SSL_ENABLED
1282 tls::const_session_t ssl = nullptr;
1283 tls::PeerCert peer_cert() const;
1284 std::string sni() const;
1285#endif
1286};
1287
1288struct Response {
1289 std::string version;
1290 int status = -1;
1291 std::string reason;
1294 std::string body;
1295 std::string location; // Redirect location
1296
1297 // User-defined context — set by pre-routing/pre-request handlers and read
1298 // by route handlers to pass arbitrary data (e.g. decoded auth tokens).
1299 std::map<std::string, any> user_data;
1300
1301 bool has_header(const std::string &key) const;
1302 std::string get_header_value(const std::string &key, const char *def = "",
1303 size_t id = 0) const;
1304 size_t get_header_value_u64(const std::string &key, size_t def = 0,
1305 size_t id = 0) const;
1306 size_t get_header_value_count(const std::string &key) const;
1307 void set_header(const std::string &key, const std::string &val);
1308
1309 bool has_trailer(const std::string &key) const;
1310 std::string get_trailer_value(const std::string &key, size_t id = 0) const;
1311 size_t get_trailer_value_count(const std::string &key) const;
1312
1313 void set_redirect(const std::string &url, int status = StatusCode::Found_302);
1314 void set_content(const char *s, size_t n, const std::string &content_type);
1315 void set_content(const std::string &s, const std::string &content_type);
1316 void set_content(std::string &&s, const std::string &content_type);
1317
1319 size_t length, const std::string &content_type, ContentProvider provider,
1320 ContentProviderResourceReleaser resource_releaser = nullptr);
1321
1323 const std::string &content_type, ContentProviderWithoutLength provider,
1324 ContentProviderResourceReleaser resource_releaser = nullptr);
1325
1327 const std::string &content_type, ContentProviderWithoutLength provider,
1328 ContentProviderResourceReleaser resource_releaser = nullptr);
1329
1330 void set_file_content(const std::string &path,
1331 const std::string &content_type);
1332 void set_file_content(const std::string &path);
1333
1334 Response() = default;
1335 Response(const Response &) = default;
1336 Response &operator=(const Response &) = default;
1337 Response(Response &&) = default;
1344
1345 // private members...
1353};
1354
1394
1395std::string to_string(Error error);
1396
1397std::ostream &operator<<(std::ostream &os, const Error &obj);
1398
1399class Stream {
1400public:
1401 virtual ~Stream() = default;
1402
1403 virtual bool is_readable() const = 0;
1404 virtual bool wait_readable() const = 0;
1405 virtual bool wait_writable() const = 0;
1406 virtual bool is_peer_alive() const { return wait_writable(); }
1407
1408 virtual ssize_t read(char *ptr, size_t size) = 0;
1409 virtual ssize_t write(const char *ptr, size_t size) = 0;
1410 virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
1411 virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
1412 virtual socket_t socket() const = 0;
1413
1414 virtual time_t duration() const = 0;
1415
1416 virtual void set_read_timeout(time_t sec, time_t usec = 0) {
1417 (void)sec;
1418 (void)usec;
1419 }
1420
1421 ssize_t write(const char *ptr);
1422 ssize_t write(const std::string &s);
1423
1424 Error get_error() const { return error_; }
1425
1426protected:
1428};
1429
1431public:
1432 TaskQueue() = default;
1433 virtual ~TaskQueue() = default;
1434
1435 virtual bool enqueue(std::function<void()> fn) = 0;
1436 virtual void shutdown() = 0;
1437
1438 virtual void on_idle() {}
1439};
1440
1441class ThreadPool final : public TaskQueue {
1442public:
1443 explicit ThreadPool(size_t n, size_t max_n = 0, size_t mqr = 0);
1444 ThreadPool(const ThreadPool &) = delete;
1445 ~ThreadPool() override = default;
1446
1447 bool enqueue(std::function<void()> fn) override;
1448 void shutdown() override;
1449
1450private:
1451 void worker(bool is_dynamic);
1452 void move_to_finished(std::thread::id id);
1453 void cleanup_finished_threads();
1454
1455 size_t base_thread_count_;
1456 size_t max_thread_count_;
1457 size_t max_queued_requests_;
1458 size_t idle_thread_count_;
1459
1460 bool shutdown_;
1461
1462 std::list<std::function<void()>> jobs_;
1463 std::vector<std::thread> threads_; // base threads
1464 std::list<std::thread> dynamic_threads_; // dynamic threads
1465 std::vector<std::thread>
1466 finished_threads_; // exited dynamic threads awaiting join
1467
1468 std::condition_variable cond_;
1469 std::mutex mutex_;
1470};
1471
1472using Logger = std::function<void(const Request &, const Response &)>;
1473
1474// Forward declaration for Error type
1475enum class Error;
1476using ErrorLogger = std::function<void(const Error &, const Request *)>;
1477
1478using SocketOptions = std::function<void(socket_t sock)>;
1479
1481
1482const char *status_message(int status);
1483
1484std::string to_string(Error error);
1485
1486std::ostream &operator<<(std::ostream &os, const Error &obj);
1487
1488std::string get_bearer_token_auth(const Request &req);
1489
1490namespace detail {
1491
1493public:
1494 MatcherBase(std::string pattern) : pattern_(std::move(pattern)) {}
1495 virtual ~MatcherBase() = default;
1496
1497 const std::string &pattern() const { return pattern_; }
1498
1499 // Match request path and populate its matches and
1500 virtual bool match(Request &request) const = 0;
1501
1502private:
1503 std::string pattern_;
1504};
1505
1524class PathParamsMatcher final : public MatcherBase {
1525public:
1526 PathParamsMatcher(const std::string &pattern);
1527
1528 bool match(Request &request) const override;
1529
1530private:
1531 // Treat segment separators as the end of path parameter capture
1532 // Does not need to handle query parameters as they are parsed before path
1533 // matching
1534 static constexpr char separator = '/';
1535
1536 // Contains static path fragments to match against, excluding the '/' after
1537 // path params
1538 // Fragments are separated by path params
1539 std::vector<std::string> static_fragments_;
1540 // Stores the names of the path parameters to be used as keys in the
1541 // Request::path_params map
1542 std::vector<std::string> param_names_;
1543};
1544
1553class RegexMatcher final : public MatcherBase {
1554public:
1555 RegexMatcher(const std::string &pattern)
1556 : MatcherBase(pattern), regex_(pattern) {}
1557
1558 bool match(Request &request) const override;
1559
1560private:
1561 std::regex regex_;
1562};
1563
1564int close_socket(socket_t sock);
1565
1566ssize_t write_headers(Stream &strm, const Headers &headers);
1567
1568bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec,
1569 time_t usec);
1570
1571} // namespace detail
1572
1573class Server {
1574public:
1575 using Handler = std::function<void(const Request &, Response &)>;
1576
1578 std::function<void(const Request &, Response &, std::exception_ptr ep)>;
1579
1585 std::function<HandlerResponse(const Request &, Response &)>;
1586
1587 using HandlerWithContentReader = std::function<void(
1588 const Request &, Response &, const ContentReader &content_reader)>;
1589
1591 std::function<int(const Request &, Response &)>;
1592
1594 std::function<void(const Request &, ws::WebSocket &)>;
1596 std::function<std::string(const std::vector<std::string> &protocols)>;
1597
1598 Server();
1599
1600 virtual ~Server();
1601
1602 virtual bool is_valid() const;
1603
1604 Server &Get(const std::string &pattern, Handler handler);
1605 Server &Post(const std::string &pattern, Handler handler);
1606 Server &Post(const std::string &pattern, HandlerWithContentReader handler);
1607 Server &Put(const std::string &pattern, Handler handler);
1608 Server &Put(const std::string &pattern, HandlerWithContentReader handler);
1609 Server &Patch(const std::string &pattern, Handler handler);
1610 Server &Patch(const std::string &pattern, HandlerWithContentReader handler);
1611 Server &Delete(const std::string &pattern, Handler handler);
1612 Server &Delete(const std::string &pattern, HandlerWithContentReader handler);
1613 Server &Options(const std::string &pattern, Handler handler);
1614
1615 Server &WebSocket(const std::string &pattern, WebSocketHandler handler);
1616 Server &WebSocket(const std::string &pattern, WebSocketHandler handler,
1617 SubProtocolSelector sub_protocol_selector);
1618
1619 bool set_base_dir(const std::string &dir,
1620 const std::string &mount_point = std::string());
1621 bool set_mount_point(const std::string &mount_point, const std::string &dir,
1622 Headers headers = Headers());
1623 bool remove_mount_point(const std::string &mount_point);
1624 Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
1625 const std::string &mime);
1626 Server &set_default_file_mimetype(const std::string &mime);
1628
1629 template <class ErrorHandlerFunc>
1630 Server &set_error_handler(ErrorHandlerFunc &&handler) {
1631 return set_error_handler_core(
1632 std::forward<ErrorHandlerFunc>(handler),
1633 std::is_convertible<ErrorHandlerFunc, HandlerWithResponse>{});
1634 }
1635
1636 Server &set_exception_handler(ExceptionHandler handler);
1637
1638 Server &set_pre_routing_handler(HandlerWithResponse handler);
1639 Server &set_post_routing_handler(Handler handler);
1640
1641 Server &set_pre_request_handler(HandlerWithResponse handler);
1642
1643 Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);
1644 Server &set_logger(Logger logger);
1645 Server &set_pre_compression_logger(Logger logger);
1646 Server &set_error_logger(ErrorLogger error_logger);
1647
1648 Server &set_address_family(int family);
1649 Server &set_tcp_nodelay(bool on);
1650 Server &set_ipv6_v6only(bool on);
1651 Server &set_socket_options(SocketOptions socket_options);
1652
1653 Server &set_default_headers(Headers headers);
1654 Server &
1655 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
1656
1657 Server &set_trusted_proxies(const std::vector<std::string> &proxies);
1658
1659 Server &set_keep_alive_max_count(size_t count);
1660 Server &set_keep_alive_timeout(time_t sec);
1661
1662 Server &set_read_timeout(time_t sec, time_t usec = 0);
1663 template <class Rep, class Period>
1664 Server &set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
1665
1666 Server &set_write_timeout(time_t sec, time_t usec = 0);
1667 template <class Rep, class Period>
1668 Server &set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
1669
1670 Server &set_idle_interval(time_t sec, time_t usec = 0);
1671 template <class Rep, class Period>
1672 Server &set_idle_interval(const std::chrono::duration<Rep, Period> &duration);
1673
1674 Server &set_payload_max_length(size_t length);
1675
1676 Server &set_websocket_ping_interval(time_t sec);
1677 template <class Rep, class Period>
1678 Server &set_websocket_ping_interval(
1679 const std::chrono::duration<Rep, Period> &duration);
1680
1681 bool bind_to_port(const std::string &host, int port, int socket_flags = 0);
1682 int bind_to_any_port(const std::string &host, int socket_flags = 0);
1683 bool listen_after_bind();
1684
1685 bool listen(const std::string &host, int port, int socket_flags = 0);
1686
1687 bool is_running() const;
1688 void wait_until_ready() const;
1689 void stop();
1690 void decommission();
1691
1692 std::function<TaskQueue *(void)> new_task_queue;
1693
1694protected:
1695 bool process_request(Stream &strm, const std::string &remote_addr,
1696 int remote_port, const std::string &local_addr,
1697 int local_port, bool close_connection,
1698 bool &connection_closed,
1699 const std::function<void(Request &)> &setup_request,
1700 bool *websocket_upgraded = nullptr);
1701
1702 std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
1703
1704 std::vector<std::string> trusted_proxies_;
1705
1717
1718private:
1719 using Handlers =
1720 std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, Handler>>;
1721 using HandlersForContentReader =
1722 std::vector<std::pair<std::unique_ptr<detail::MatcherBase>,
1724
1725 static std::unique_ptr<detail::MatcherBase>
1726 make_matcher(const std::string &pattern);
1727
1728 Server &set_error_handler_core(HandlerWithResponse handler, std::true_type);
1729 Server &set_error_handler_core(Handler handler, std::false_type);
1730
1731 socket_t create_server_socket(const std::string &host, int port,
1732 int socket_flags,
1733 SocketOptions socket_options) const;
1734 int bind_internal(const std::string &host, int port, int socket_flags);
1735 bool listen_internal();
1736
1737 bool routing(Request &req, Response &res, Stream &strm);
1738 bool handle_file_request(Request &req, Response &res);
1739 bool check_if_not_modified(const Request &req, Response &res,
1740 const std::string &etag, time_t mtime) const;
1741 bool check_if_range(Request &req, const std::string &etag,
1742 time_t mtime) const;
1743 bool dispatch_request(Request &req, Response &res,
1744 const Handlers &handlers) const;
1745 bool dispatch_request_for_content_reader(
1746 Request &req, Response &res, ContentReader content_reader,
1747 const HandlersForContentReader &handlers) const;
1748
1749 bool parse_request_line(const char *s, Request &req) const;
1750 void apply_ranges(const Request &req, Response &res,
1751 std::string &content_type, std::string &boundary) const;
1752 bool write_response(Stream &strm, bool close_connection, Request &req,
1753 Response &res);
1754 bool write_response_with_content(Stream &strm, bool close_connection,
1755 const Request &req, Response &res);
1756 bool write_response_core(Stream &strm, bool close_connection,
1757 const Request &req, Response &res,
1758 bool need_apply_ranges);
1759 bool write_content_with_provider(Stream &strm, const Request &req,
1760 Response &res, const std::string &boundary,
1761 const std::string &content_type);
1762 bool read_content(Stream &strm, Request &req, Response &res);
1763 bool read_content_with_content_receiver(Stream &strm, Request &req,
1764 Response &res,
1765 ContentReceiver receiver,
1766 FormDataHeader multipart_header,
1767 ContentReceiver multipart_receiver);
1768 bool read_content_core(Stream &strm, Request &req, Response &res,
1769 ContentReceiver receiver,
1770 FormDataHeader multipart_header,
1771 ContentReceiver multipart_receiver) const;
1772
1773 virtual bool process_and_close_socket(socket_t sock);
1774
1775 void output_log(const Request &req, const Response &res) const;
1776 void output_pre_compression_log(const Request &req,
1777 const Response &res) const;
1778 void output_error_log(const Error &err, const Request *req) const;
1779
1780 std::atomic<bool> is_running_{false};
1781 std::atomic<bool> is_decommissioned{false};
1782
1783 struct MountPointEntry {
1784 std::string mount_point;
1785 std::string base_dir;
1786 std::string resolved_base_dir;
1787 Headers headers;
1788 };
1789 std::vector<MountPointEntry> base_dirs_;
1790 std::map<std::string, std::string> file_extension_and_mimetype_map_;
1791 std::string default_file_mimetype_ = "application/octet-stream";
1792 Handler file_request_handler_;
1793
1794 Handlers get_handlers_;
1795 Handlers post_handlers_;
1796 HandlersForContentReader post_handlers_for_content_reader_;
1797 Handlers put_handlers_;
1798 HandlersForContentReader put_handlers_for_content_reader_;
1799 Handlers patch_handlers_;
1800 HandlersForContentReader patch_handlers_for_content_reader_;
1801 Handlers delete_handlers_;
1802 HandlersForContentReader delete_handlers_for_content_reader_;
1803 Handlers options_handlers_;
1804
1805 struct WebSocketHandlerEntry {
1806 std::unique_ptr<detail::MatcherBase> matcher;
1807 WebSocketHandler handler;
1808 SubProtocolSelector sub_protocol_selector;
1809 };
1810 using WebSocketHandlers = std::vector<WebSocketHandlerEntry>;
1811 WebSocketHandlers websocket_handlers_;
1812
1813 HandlerWithResponse error_handler_;
1814 ExceptionHandler exception_handler_;
1815 HandlerWithResponse pre_routing_handler_;
1816 Handler post_routing_handler_;
1817 HandlerWithResponse pre_request_handler_;
1818 Expect100ContinueHandler expect_100_continue_handler_;
1819
1820 mutable std::mutex logger_mutex_;
1821 Logger logger_;
1822 Logger pre_compression_logger_;
1823 ErrorLogger error_logger_;
1824
1825 int address_family_ = AF_UNSPEC;
1826 bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
1827 bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
1828 SocketOptions socket_options_ = default_socket_options;
1829
1830 Headers default_headers_;
1831 std::function<ssize_t(Stream &, Headers &)> header_writer_ =
1832 detail::write_headers;
1833};
1834
1835class Result {
1836public:
1837 Result() = default;
1838 Result(std::unique_ptr<Response> &&res, Error err,
1839 Headers &&request_headers = Headers{})
1840 : res_(std::move(res)), err_(err),
1841 request_headers_(std::move(request_headers)) {}
1842 // Response
1843 operator bool() const { return res_ != nullptr; }
1844 bool operator==(std::nullptr_t) const { return res_ == nullptr; }
1845 bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
1846 const Response &value() const { return *res_; }
1847 Response &value() { return *res_; }
1848 const Response &operator*() const { return *res_; }
1849 Response &operator*() { return *res_; }
1850 const Response *operator->() const { return res_.get(); }
1851 Response *operator->() { return res_.get(); }
1852
1853 // Error
1854 Error error() const { return err_; }
1855
1856 // Request Headers
1857 bool has_request_header(const std::string &key) const;
1858 std::string get_request_header_value(const std::string &key,
1859 const char *def = "",
1860 size_t id = 0) const;
1861 size_t get_request_header_value_u64(const std::string &key, size_t def = 0,
1862 size_t id = 0) const;
1863 size_t get_request_header_value_count(const std::string &key) const;
1864
1865private:
1866 std::unique_ptr<Response> res_;
1867 Error err_ = Error::Unknown;
1868 Headers request_headers_;
1869
1870#ifdef CPPHTTPLIB_SSL_ENABLED
1871public:
1872 Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
1873 int ssl_error)
1874 : res_(std::move(res)), err_(err),
1875 request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {}
1876 Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
1877 int ssl_error, uint64_t ssl_backend_error)
1878 : res_(std::move(res)), err_(err),
1879 request_headers_(std::move(request_headers)), ssl_error_(ssl_error),
1880 ssl_backend_error_(ssl_backend_error) {}
1881
1882 int ssl_error() const { return ssl_error_; }
1883 uint64_t ssl_backend_error() const { return ssl_backend_error_; }
1884
1885private:
1886 int ssl_error_ = 0;
1887 uint64_t ssl_backend_error_ = 0;
1888#endif
1889
1890#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1891public:
1892 [[deprecated("Use ssl_backend_error() instead. "
1893 "This function will be removed by v1.0.0.")]]
1894 uint64_t ssl_openssl_error() const {
1895 return ssl_backend_error_;
1896 }
1897#endif
1898};
1899
1902
1903 bool is_open() const { return sock != INVALID_SOCKET; }
1904
1905 ClientConnection() = default;
1906
1908
1911
1913 : sock(other.sock)
1914#ifdef CPPHTTPLIB_SSL_ENABLED
1915 ,
1916 session(other.session)
1917#endif
1918 {
1919 other.sock = INVALID_SOCKET;
1920#ifdef CPPHTTPLIB_SSL_ENABLED
1921 other.session = nullptr;
1922#endif
1923 }
1924
1926 if (this != &other) {
1927 sock = other.sock;
1928 other.sock = INVALID_SOCKET;
1929#ifdef CPPHTTPLIB_SSL_ENABLED
1930 session = other.session;
1931 other.session = nullptr;
1932#endif
1933 }
1934 return *this;
1935 }
1936
1937#ifdef CPPHTTPLIB_SSL_ENABLED
1938 tls::session_t session = nullptr;
1939#endif
1940};
1941
1942namespace detail {
1943
1944struct ChunkedDecoder;
1945
1947 Stream *stream = nullptr;
1949 size_t content_length = 0;
1951 size_t bytes_read = 0;
1952 bool chunked = false;
1953 bool eof = false;
1954 std::unique_ptr<ChunkedDecoder> chunked_decoder;
1956
1957 ssize_t read(char *buf, size_t len);
1958 bool has_error() const { return last_error != Error::Success; }
1959};
1960
1961inline ssize_t read_body_content(Stream *stream, BodyReader &br, char *buf,
1962 size_t len) {
1963 (void)stream;
1964 return br.read(buf, len);
1965}
1966
1967class decompressor;
1968
1969} // namespace detail
1970
1972public:
1973 explicit ClientImpl(const std::string &host);
1974
1975 explicit ClientImpl(const std::string &host, int port);
1976
1977 explicit ClientImpl(const std::string &host, int port,
1978 const std::string &client_cert_path,
1979 const std::string &client_key_path);
1980
1981 virtual ~ClientImpl();
1982
1983 virtual bool is_valid() const;
1984
1986 std::unique_ptr<Response> response;
1988
1989 StreamHandle() = default;
1990 StreamHandle(const StreamHandle &) = delete;
1994 ~StreamHandle() = default;
1995
1996 bool is_valid() const {
1997 return response != nullptr && error == Error::Success;
1998 }
1999
2000 ssize_t read(char *buf, size_t len);
2001 void parse_trailers_if_needed();
2002 Error get_read_error() const { return body_reader_.last_error; }
2003 bool has_read_error() const { return body_reader_.has_error(); }
2004
2005 bool trailers_parsed_ = false;
2006
2007 private:
2008 friend class ClientImpl;
2009
2010 ssize_t read_with_decompression(char *buf, size_t len);
2011
2012 std::unique_ptr<ClientConnection> connection_;
2013 std::unique_ptr<Stream> socket_stream_;
2014 Stream *stream_ = nullptr;
2015 detail::BodyReader body_reader_;
2016
2017 std::unique_ptr<detail::decompressor> decompressor_;
2018 std::string decompress_buffer_;
2019 size_t decompress_offset_ = 0;
2020 size_t decompressed_bytes_read_ = 0;
2021 };
2022
2023 // clang-format off
2024 Result Get(const std::string &path, DownloadProgress progress = nullptr);
2025 Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2026 Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2027 Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2028 Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2029 Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2030 Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
2031 Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2032 Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2033
2034 Result Head(const std::string &path);
2035 Result Head(const std::string &path, const Headers &headers);
2036
2037 Result Post(const std::string &path);
2038 Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2039 Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2040 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2041 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2042 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2043 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2044 Result Post(const std::string &path, const Params &params);
2045 Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2046 Result Post(const std::string &path, const Headers &headers);
2047 Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2048 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2049 Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2050 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);
2051 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2052 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2053 Result Post(const std::string &path, const Headers &headers, const Params &params);
2054 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2055 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2056 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2057 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2058
2059 Result Put(const std::string &path);
2060 Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2061 Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2062 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2063 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2064 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2065 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2066 Result Put(const std::string &path, const Params &params);
2067 Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2068 Result Put(const std::string &path, const Headers &headers);
2069 Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2070 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2071 Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2072 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);
2073 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2074 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2075 Result Put(const std::string &path, const Headers &headers, const Params &params);
2076 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2077 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2078 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2079 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2080
2081 Result Patch(const std::string &path);
2082 Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2083 Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2084 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2085 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2086 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2087 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2088 Result Patch(const std::string &path, const Params &params);
2089 Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2090 Result Patch(const std::string &path, const Headers &headers, UploadProgress progress = nullptr);
2091 Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2092 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2093 Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2094 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);
2095 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2096 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2097 Result Patch(const std::string &path, const Headers &headers, const Params &params);
2098 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2099 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2100 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2101 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2102
2103 Result Delete(const std::string &path, DownloadProgress progress = nullptr);
2104 Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2105 Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2106 Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
2107 Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2108 Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2109 Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2110 Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);
2111
2112 Result Options(const std::string &path);
2113 Result Options(const std::string &path, const Headers &headers);
2114 // clang-format on
2115
2116 // Streaming API: Open a stream for reading response body incrementally
2117 // Socket ownership is transferred to StreamHandle for true streaming
2118 // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
2119 StreamHandle open_stream(const std::string &method, const std::string &path,
2120 const Params &params = {},
2121 const Headers &headers = {},
2122 const std::string &body = {},
2123 const std::string &content_type = {});
2124
2125 bool send(Request &req, Response &res, Error &error);
2126 Result send(const Request &req);
2127
2128 void stop();
2129
2130 std::string host() const;
2131 int port() const;
2132
2133 size_t is_socket_open() const;
2134 socket_t socket() const;
2135
2136 void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
2137
2138 void set_default_headers(Headers headers);
2139
2140 void
2141 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
2142
2143 void set_address_family(int family);
2144 void set_tcp_nodelay(bool on);
2145 void set_ipv6_v6only(bool on);
2146 void set_socket_options(SocketOptions socket_options);
2147
2148 void set_connection_timeout(time_t sec, time_t usec = 0);
2149 template <class Rep, class Period>
2150 void
2151 set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
2152
2153 void set_read_timeout(time_t sec, time_t usec = 0);
2154 template <class Rep, class Period>
2155 void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
2156
2157 void set_write_timeout(time_t sec, time_t usec = 0);
2158 template <class Rep, class Period>
2159 void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
2160
2161 void set_max_timeout(time_t msec);
2162 template <class Rep, class Period>
2163 void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
2164
2165 void set_basic_auth(const std::string &username, const std::string &password);
2166 void set_bearer_token_auth(const std::string &token);
2167
2168 void set_keep_alive(bool on);
2169 void set_follow_location(bool on);
2170
2171 void set_path_encode(bool on);
2172
2173 void set_compress(bool on);
2174
2175 void set_decompress(bool on);
2176
2177 void set_payload_max_length(size_t length);
2178
2179 void set_interface(const std::string &intf);
2180
2181 void set_proxy(const std::string &host, int port);
2182 void set_proxy_basic_auth(const std::string &username,
2183 const std::string &password);
2184 void set_proxy_bearer_token_auth(const std::string &token);
2185
2186 void set_logger(Logger logger);
2187 void set_error_logger(ErrorLogger error_logger);
2188
2189protected:
2190 struct Socket {
2192
2193 // For Mbed TLS compatibility: start_time for request timeout tracking
2194 std::chrono::time_point<std::chrono::steady_clock> start_time_;
2195
2196 bool is_open() const { return sock != INVALID_SOCKET; }
2197
2198#ifdef CPPHTTPLIB_SSL_ENABLED
2199 tls::session_t ssl = nullptr;
2200#endif
2201 };
2202
2203 virtual bool create_and_connect_socket(Socket &socket, Error &error);
2204 virtual bool ensure_socket_connection(Socket &socket, Error &error);
2205 virtual bool setup_proxy_connection(
2206 Socket &socket,
2207 std::chrono::time_point<std::chrono::steady_clock> start_time,
2208 Response &res, bool &success, Error &error);
2209
2210 // All of:
2211 // shutdown_ssl
2212 // shutdown_socket
2213 // close_socket
2214 // should ONLY be called when socket_mutex_ is locked.
2215 // Also, shutdown_ssl and close_socket should also NOT be called concurrently
2216 // with a DIFFERENT thread sending requests using that socket.
2217 virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
2218 void shutdown_socket(Socket &socket) const;
2219 void close_socket(Socket &socket);
2220
2221 bool process_request(Stream &strm, Request &req, Response &res,
2222 bool close_connection, Error &error);
2223
2224 bool write_content_with_provider(Stream &strm, const Request &req,
2225 Error &error) const;
2226
2227 void copy_settings(const ClientImpl &rhs);
2228
2229 void output_log(const Request &req, const Response &res) const;
2230 void output_error_log(const Error &err, const Request *req) const;
2231
2232 // Socket endpoint information
2233 const std::string host_;
2234 const int port_;
2235
2236 // Current open socket
2238 mutable std::mutex socket_mutex_;
2239 std::recursive_mutex request_mutex_;
2240
2241 // These are all protected under socket_mutex
2243 std::thread::id socket_requests_are_from_thread_ = std::thread::id();
2245
2246 // Hostname-IP map
2247 std::map<std::string, std::string> addr_map_;
2248
2249 // Default headers
2251
2252 // Header writer
2253 std::function<ssize_t(Stream &, Headers &)> header_writer_ =
2255
2256 // Settings
2258 std::string client_key_path_;
2259
2267
2271
2272 bool keep_alive_ = false;
2273 bool follow_location_ = false;
2274
2275 bool path_encode_ = true;
2276
2277 int address_family_ = AF_UNSPEC;
2281
2282 bool compress_ = false;
2283 bool decompress_ = true;
2284
2287
2288 std::string interface_;
2289
2290 std::string proxy_host_;
2291 int proxy_port_ = -1;
2292
2296
2297 mutable std::mutex logger_mutex_;
2300
2301private:
2302 bool send_(Request &req, Response &res, Error &error);
2303 Result send_(Request &&req);
2304
2305 socket_t create_client_socket(Error &error) const;
2306 bool read_response_line(Stream &strm, const Request &req, Response &res,
2307 bool skip_100_continue = true) const;
2308 bool write_request(Stream &strm, Request &req, bool close_connection,
2309 Error &error, bool skip_body = false);
2310 bool write_request_body(Stream &strm, Request &req, Error &error);
2311 void prepare_default_headers(Request &r, bool for_stream,
2312 const std::string &ct);
2313 bool redirect(Request &req, Response &res, Error &error);
2314 bool create_redirect_client(const std::string &scheme,
2315 const std::string &host, int port, Request &req,
2316 Response &res, const std::string &path,
2317 const std::string &location, Error &error);
2318 template <typename ClientType> void setup_redirect_client(ClientType &client);
2319 bool handle_request(Stream &strm, Request &req, Response &res,
2320 bool close_connection, Error &error);
2321 std::unique_ptr<Response> send_with_content_provider_and_receiver(
2322 Request &req, const char *body, size_t content_length,
2323 ContentProvider content_provider,
2324 ContentProviderWithoutLength content_provider_without_length,
2325 const std::string &content_type, ContentReceiver content_receiver,
2326 Error &error);
2327 Result send_with_content_provider_and_receiver(
2328 const std::string &method, const std::string &path,
2329 const Headers &headers, const char *body, size_t content_length,
2330 ContentProvider content_provider,
2331 ContentProviderWithoutLength content_provider_without_length,
2332 const std::string &content_type, ContentReceiver content_receiver,
2333 UploadProgress progress);
2334 ContentProviderWithoutLength get_multipart_content_provider(
2335 const std::string &boundary, const UploadFormDataItems &items,
2336 const FormDataProviderItems &provider_items) const;
2337
2338 virtual bool
2339 process_socket(const Socket &socket,
2340 std::chrono::time_point<std::chrono::steady_clock> start_time,
2341 std::function<bool(Stream &strm)> callback);
2342 virtual bool is_ssl() const;
2343
2344 void transfer_socket_ownership_to_handle(StreamHandle &handle);
2345
2346#ifdef CPPHTTPLIB_SSL_ENABLED
2347public:
2348 void set_digest_auth(const std::string &username,
2349 const std::string &password);
2350 void set_proxy_digest_auth(const std::string &username,
2351 const std::string &password);
2352 void set_ca_cert_path(const std::string &ca_cert_file_path,
2353 const std::string &ca_cert_dir_path = std::string());
2354 void enable_server_certificate_verification(bool enabled);
2355 void enable_server_hostname_verification(bool enabled);
2356
2357protected:
2358 std::string digest_auth_username_;
2359 std::string digest_auth_password_;
2360 std::string proxy_digest_auth_username_;
2361 std::string proxy_digest_auth_password_;
2362 std::string ca_cert_file_path_;
2363 std::string ca_cert_dir_path_;
2364 bool server_certificate_verification_ = true;
2365 bool server_hostname_verification_ = true;
2366 std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer
2367 int last_ssl_error_ = 0;
2368 uint64_t last_backend_error_ = 0;
2369#endif
2370
2371#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2372public:
2373 [[deprecated("Use load_ca_cert_store() instead. "
2374 "This function will be removed by v1.0.0.")]]
2375 void set_ca_cert_store(X509_STORE *ca_cert_store);
2376
2377 [[deprecated("Use tls::create_ca_store() instead. "
2378 "This function will be removed by v1.0.0.")]]
2379 X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const;
2380
2381 [[deprecated("Use set_server_certificate_verifier(VerifyCallback) instead. "
2382 "This function will be removed by v1.0.0.")]]
2383 virtual void set_server_certificate_verifier(
2384 std::function<SSLVerifierResponse(SSL *ssl)> verifier);
2385#endif
2386};
2387
2388class Client {
2389public:
2390 // Universal interface
2391 explicit Client(const std::string &scheme_host_port);
2392
2393 explicit Client(const std::string &scheme_host_port,
2394 const std::string &client_cert_path,
2395 const std::string &client_key_path);
2396
2397 // HTTP only interface
2398 explicit Client(const std::string &host, int port);
2399
2400 explicit Client(const std::string &host, int port,
2401 const std::string &client_cert_path,
2402 const std::string &client_key_path);
2403
2404 Client(Client &&) = default;
2405 Client &operator=(Client &&) = default;
2406
2408
2409 bool is_valid() const;
2410
2411 // clang-format off
2412 Result Get(const std::string &path, DownloadProgress progress = nullptr);
2413 Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2414 Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2415 Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2416 Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2417 Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2418 Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
2419 Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2420 Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2421
2422 Result Head(const std::string &path);
2423 Result Head(const std::string &path, const Headers &headers);
2424
2425 Result Post(const std::string &path);
2426 Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2427 Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2428 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2429 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2430 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2431 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2432 Result Post(const std::string &path, const Params &params);
2433 Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2434 Result Post(const std::string &path, const Headers &headers);
2435 Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2436 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2437 Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2438 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);
2439 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2440 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2441 Result Post(const std::string &path, const Headers &headers, const Params &params);
2442 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2443 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2444 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2445 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2446
2447 Result Put(const std::string &path);
2448 Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2449 Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2450 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2451 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2452 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2453 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2454 Result Put(const std::string &path, const Params &params);
2455 Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2456 Result Put(const std::string &path, const Headers &headers);
2457 Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2458 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2459 Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2460 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);
2461 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2462 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2463 Result Put(const std::string &path, const Headers &headers, const Params &params);
2464 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2465 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2466 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2467 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2468
2469 Result Patch(const std::string &path);
2470 Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2471 Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2472 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2473 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2474 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2475 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2476 Result Patch(const std::string &path, const Params &params);
2477 Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2478 Result Patch(const std::string &path, const Headers &headers);
2479 Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2480 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2481 Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2482 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);
2483 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2484 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2485 Result Patch(const std::string &path, const Headers &headers, const Params &params);
2486 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2487 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2488 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2489 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2490
2491 Result Delete(const std::string &path, DownloadProgress progress = nullptr);
2492 Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2493 Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2494 Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
2495 Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2496 Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2497 Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2498 Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);
2499
2500 Result Options(const std::string &path);
2501 Result Options(const std::string &path, const Headers &headers);
2502 // clang-format on
2503
2504 // Streaming API: Open a stream for reading response body incrementally
2505 // Socket ownership is transferred to StreamHandle for true streaming
2506 // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
2507 ClientImpl::StreamHandle open_stream(const std::string &method,
2508 const std::string &path,
2509 const Params &params = {},
2510 const Headers &headers = {},
2511 const std::string &body = {},
2512 const std::string &content_type = {});
2513
2514 bool send(Request &req, Response &res, Error &error);
2515 Result send(const Request &req);
2516
2517 void stop();
2518
2519 std::string host() const;
2520 int port() const;
2521
2522 size_t is_socket_open() const;
2523 socket_t socket() const;
2524
2525 void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
2526
2527 void set_default_headers(Headers headers);
2528
2529 void
2530 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
2531
2532 void set_address_family(int family);
2533 void set_tcp_nodelay(bool on);
2534 void set_socket_options(SocketOptions socket_options);
2535
2536 void set_connection_timeout(time_t sec, time_t usec = 0);
2537 template <class Rep, class Period>
2538 void
2539 set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
2540
2541 void set_read_timeout(time_t sec, time_t usec = 0);
2542 template <class Rep, class Period>
2543 void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
2544
2545 void set_write_timeout(time_t sec, time_t usec = 0);
2546 template <class Rep, class Period>
2547 void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
2548
2549 void set_max_timeout(time_t msec);
2550 template <class Rep, class Period>
2551 void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
2552
2553 void set_basic_auth(const std::string &username, const std::string &password);
2554 void set_bearer_token_auth(const std::string &token);
2555
2556 void set_keep_alive(bool on);
2557 void set_follow_location(bool on);
2558
2559 void set_path_encode(bool on);
2560 void set_url_encode(bool on);
2561
2562 void set_compress(bool on);
2563
2564 void set_decompress(bool on);
2565
2566 void set_payload_max_length(size_t length);
2567
2568 void set_interface(const std::string &intf);
2569
2570 void set_proxy(const std::string &host, int port);
2571 void set_proxy_basic_auth(const std::string &username,
2572 const std::string &password);
2573 void set_proxy_bearer_token_auth(const std::string &token);
2574 void set_logger(Logger logger);
2575 void set_error_logger(ErrorLogger error_logger);
2576
2577private:
2578 std::unique_ptr<ClientImpl> cli_;
2579
2580#ifdef CPPHTTPLIB_SSL_ENABLED
2581public:
2582 void set_digest_auth(const std::string &username,
2583 const std::string &password);
2584 void set_proxy_digest_auth(const std::string &username,
2585 const std::string &password);
2586 void enable_server_certificate_verification(bool enabled);
2587 void enable_server_hostname_verification(bool enabled);
2588 void set_ca_cert_path(const std::string &ca_cert_file_path,
2589 const std::string &ca_cert_dir_path = std::string());
2590
2591 void set_ca_cert_store(tls::ca_store_t ca_cert_store);
2592 void load_ca_cert_store(const char *ca_cert, std::size_t size);
2593
2594 void set_server_certificate_verifier(tls::VerifyCallback verifier);
2595
2596 void set_session_verifier(
2597 std::function<SSLVerifierResponse(tls::session_t)> verifier);
2598
2599 tls::ctx_t tls_context() const;
2600
2601#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2602 void enable_windows_certificate_verification(bool enabled);
2603#endif
2604
2605private:
2606 bool is_ssl_ = false;
2607#endif
2608
2609#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2610public:
2611 [[deprecated("Use tls_context() instead. "
2612 "This function will be removed by v1.0.0.")]]
2613 SSL_CTX *ssl_context() const;
2614
2615 [[deprecated("Use set_session_verifier(session_t) instead. "
2616 "This function will be removed by v1.0.0.")]]
2617 void set_server_certificate_verifier(
2618 std::function<SSLVerifierResponse(SSL *ssl)> verifier);
2619
2620 [[deprecated("Use Result::ssl_backend_error() instead. "
2621 "This function will be removed by v1.0.0.")]]
2622 long get_verify_result() const;
2623#endif
2624};
2625
2626#ifdef CPPHTTPLIB_SSL_ENABLED
2627class SSLServer : public Server {
2628public:
2629 SSLServer(const char *cert_path, const char *private_key_path,
2630 const char *client_ca_cert_file_path = nullptr,
2631 const char *client_ca_cert_dir_path = nullptr,
2632 const char *private_key_password = nullptr);
2633
2634 struct PemMemory {
2635 const char *cert_pem;
2636 size_t cert_pem_len;
2637 const char *key_pem;
2638 size_t key_pem_len;
2639 const char *client_ca_pem;
2640 size_t client_ca_pem_len;
2641 const char *private_key_password;
2642 };
2643 explicit SSLServer(const PemMemory &pem);
2644
2645 // The callback receives the ctx_t handle which can be cast to the
2646 // appropriate backend type (SSL_CTX* for OpenSSL,
2647 // tls::impl::MbedTlsContext* for Mbed TLS)
2648 explicit SSLServer(const tls::ContextSetupCallback &setup_callback);
2649
2650 ~SSLServer() override;
2651
2652 bool is_valid() const override;
2653
2654 bool update_certs_pem(const char *cert_pem, const char *key_pem,
2655 const char *client_ca_pem = nullptr,
2656 const char *password = nullptr);
2657
2658 tls::ctx_t tls_context() const { return ctx_; }
2659
2660 int ssl_last_error() const { return last_ssl_error_; }
2661
2662private:
2663 bool process_and_close_socket(socket_t sock) override;
2664
2665 tls::ctx_t ctx_ = nullptr;
2666 std::mutex ctx_mutex_;
2667
2668 int last_ssl_error_ = 0;
2669
2670#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2671public:
2672 [[deprecated("Use SSLServer(PemMemory) or "
2673 "SSLServer(ContextSetupCallback) instead. "
2674 "This constructor will be removed by v1.0.0.")]]
2675 SSLServer(X509 *cert, EVP_PKEY *private_key,
2676 X509_STORE *client_ca_cert_store = nullptr);
2677
2678 [[deprecated("Use SSLServer(ContextSetupCallback) instead. "
2679 "This constructor will be removed by v1.0.0.")]]
2680 SSLServer(
2681 const std::function<bool(SSL_CTX &ssl_ctx)> &setup_ssl_ctx_callback);
2682
2683 [[deprecated("Use tls_context() instead. "
2684 "This function will be removed by v1.0.0.")]]
2685 SSL_CTX *ssl_context() const;
2686
2687 [[deprecated("Use update_certs_pem() instead. "
2688 "This function will be removed by v1.0.0.")]]
2689 void update_certs(X509 *cert, EVP_PKEY *private_key,
2690 X509_STORE *client_ca_cert_store = nullptr);
2691#endif
2692};
2693
2694class SSLClient final : public ClientImpl {
2695public:
2696 explicit SSLClient(const std::string &host);
2697
2698 explicit SSLClient(const std::string &host, int port);
2699
2700 explicit SSLClient(const std::string &host, int port,
2701 const std::string &client_cert_path,
2702 const std::string &client_key_path,
2703 const std::string &private_key_password = std::string());
2704
2705 struct PemMemory {
2706 const char *cert_pem;
2707 size_t cert_pem_len;
2708 const char *key_pem;
2709 size_t key_pem_len;
2710 const char *private_key_password;
2711 };
2712 explicit SSLClient(const std::string &host, int port, const PemMemory &pem);
2713
2714 ~SSLClient() override;
2715
2716 bool is_valid() const override;
2717
2718 void set_ca_cert_store(tls::ca_store_t ca_cert_store);
2719 void load_ca_cert_store(const char *ca_cert, std::size_t size);
2720
2721 void set_server_certificate_verifier(tls::VerifyCallback verifier);
2722
2723 // Post-handshake session verifier (backend-independent)
2724 void set_session_verifier(
2725 std::function<SSLVerifierResponse(tls::session_t)> verifier);
2726
2727 tls::ctx_t tls_context() const { return ctx_; }
2728
2729#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2730 void enable_windows_certificate_verification(bool enabled);
2731#endif
2732
2733private:
2734 bool create_and_connect_socket(Socket &socket, Error &error) override;
2735 bool ensure_socket_connection(Socket &socket, Error &error) override;
2736 void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
2737 void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully);
2738
2739 bool
2740 process_socket(const Socket &socket,
2741 std::chrono::time_point<std::chrono::steady_clock> start_time,
2742 std::function<bool(Stream &strm)> callback) override;
2743 bool is_ssl() const override;
2744
2745 bool setup_proxy_connection(
2746 Socket &socket,
2747 std::chrono::time_point<std::chrono::steady_clock> start_time,
2748 Response &res, bool &success, Error &error) override;
2749 bool connect_with_proxy(
2750 Socket &sock,
2751 std::chrono::time_point<std::chrono::steady_clock> start_time,
2752 Response &res, bool &success, Error &error);
2753 bool initialize_ssl(Socket &socket, Error &error);
2754
2755 bool load_certs();
2756
2757 tls::ctx_t ctx_ = nullptr;
2758 std::mutex ctx_mutex_;
2759 std::once_flag initialize_cert_;
2760
2761 long verify_result_ = 0;
2762
2763 std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
2764
2765#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2766 bool enable_windows_cert_verification_ = true;
2767#endif
2768
2769 friend class ClientImpl;
2770
2771#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2772public:
2773 [[deprecated("Use SSLClient(host, port, PemMemory) instead. "
2774 "This constructor will be removed by v1.0.0.")]]
2775 explicit SSLClient(const std::string &host, int port, X509 *client_cert,
2776 EVP_PKEY *client_key,
2777 const std::string &private_key_password = std::string());
2778
2779 [[deprecated("Use Result::ssl_backend_error() instead. "
2780 "This function will be removed by v1.0.0.")]]
2781 long get_verify_result() const;
2782
2783 [[deprecated("Use tls_context() instead. "
2784 "This function will be removed by v1.0.0.")]]
2785 SSL_CTX *ssl_context() const;
2786
2787 [[deprecated("Use set_session_verifier(session_t) instead. "
2788 "This function will be removed by v1.0.0.")]]
2789 void set_server_certificate_verifier(
2790 std::function<SSLVerifierResponse(SSL *ssl)> verifier) override;
2791
2792private:
2793 bool verify_host(X509 *server_cert) const;
2794 bool verify_host_with_subject_alt_name(X509 *server_cert) const;
2795 bool verify_host_with_common_name(X509 *server_cert) const;
2796#endif
2797};
2798#endif // CPPHTTPLIB_SSL_ENABLED
2799
2800namespace detail {
2801
2802template <typename T, typename U>
2803inline void duration_to_sec_and_usec(const T &duration, U callback) {
2804 auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration).count();
2805 auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
2806 duration - std::chrono::seconds(sec))
2807 .count();
2808 callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
2809}
2810
2811template <size_t N> inline constexpr size_t str_len(const char (&)[N]) {
2812 return N - 1;
2813}
2814
2815inline bool is_numeric(const std::string &str) {
2816 return !str.empty() &&
2817 std::all_of(str.cbegin(), str.cend(),
2818 [](unsigned char c) { return std::isdigit(c); });
2819}
2820
2821inline size_t get_header_value_u64(const Headers &headers,
2822 const std::string &key, size_t def,
2823 size_t id, bool &is_invalid_value) {
2824 is_invalid_value = false;
2825 auto rng = headers.equal_range(key);
2826 auto it = rng.first;
2827 std::advance(it, static_cast<ssize_t>(id));
2828 if (it != rng.second) {
2829 if (is_numeric(it->second)) {
2830 return static_cast<size_t>(std::strtoull(it->second.data(), nullptr, 10));
2831 } else {
2832 is_invalid_value = true;
2833 }
2834 }
2835 return def;
2836}
2837
2838inline size_t get_header_value_u64(const Headers &headers,
2839 const std::string &key, size_t def,
2840 size_t id) {
2841 auto dummy = false;
2842 return get_header_value_u64(headers, key, def, id, dummy);
2843}
2844
2845} // namespace detail
2846
2847template <class Rep, class Period>
2848inline Server &
2849Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
2851 duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
2852 return *this;
2853}
2854
2855template <class Rep, class Period>
2856inline Server &
2857Server::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
2859 duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
2860 return *this;
2861}
2862
2863template <class Rep, class Period>
2864inline Server &
2865Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
2867 duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); });
2868 return *this;
2869}
2870
2871template <class Rep, class Period>
2873 const std::chrono::duration<Rep, Period> &duration) {
2874 detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
2875 set_connection_timeout(sec, usec);
2876 });
2877}
2878
2879template <class Rep, class Period>
2881 const std::chrono::duration<Rep, Period> &duration) {
2883 duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
2884}
2885
2886template <class Rep, class Period>
2888 const std::chrono::duration<Rep, Period> &duration) {
2890 duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
2891}
2892
2893template <class Rep, class Period>
2895 const std::chrono::duration<Rep, Period> &duration) {
2896 auto msec =
2897 std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
2898 set_max_timeout(msec);
2899}
2900
2901template <class Rep, class Period>
2903 const std::chrono::duration<Rep, Period> &duration) {
2904 cli_->set_connection_timeout(duration);
2905}
2906
2907template <class Rep, class Period>
2908inline void
2909Client::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
2910 cli_->set_read_timeout(duration);
2911}
2912
2913template <class Rep, class Period>
2914inline void
2915Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
2916 cli_->set_write_timeout(duration);
2917}
2918
2919inline void Client::set_max_timeout(time_t msec) {
2920 cli_->set_max_timeout(msec);
2921}
2922
2923template <class Rep, class Period>
2924inline void
2925Client::set_max_timeout(const std::chrono::duration<Rep, Period> &duration) {
2926 cli_->set_max_timeout(duration);
2927}
2928
2929/*
2930 * Forward declarations and types that will be part of the .h file if split into
2931 * .h + .cc.
2932 */
2933
2934std::string hosted_at(const std::string &hostname);
2935
2936void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);
2937
2938// JavaScript-style URL encoding/decoding functions
2939std::string encode_uri_component(const std::string &value);
2940std::string encode_uri(const std::string &value);
2941std::string decode_uri_component(const std::string &value);
2942std::string decode_uri(const std::string &value);
2943
2944// RFC 3986 compliant URL component encoding/decoding functions
2945std::string encode_path_component(const std::string &component);
2946std::string decode_path_component(const std::string &component);
2947std::string encode_query_component(const std::string &component,
2948 bool space_as_plus = true);
2949std::string decode_query_component(const std::string &component,
2950 bool plus_as_space = true);
2951
2952std::string sanitize_filename(const std::string &filename);
2953
2954std::string append_query_params(const std::string &path, const Params &params);
2955
2956std::pair<std::string, std::string> make_range_header(const Ranges &ranges);
2957
2958std::pair<std::string, std::string>
2959make_basic_authentication_header(const std::string &username,
2960 const std::string &password,
2961 bool is_proxy = false);
2962
2963namespace detail {
2964
2965#if defined(_WIN32)
2966inline std::wstring u8string_to_wstring(const char *s) {
2967 if (!s) { return std::wstring(); }
2968
2969 auto len = static_cast<int>(strlen(s));
2970 if (!len) { return std::wstring(); }
2971
2972 auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
2973 if (!wlen) { return std::wstring(); }
2974
2975 std::wstring ws;
2976 ws.resize(wlen);
2977 wlen = ::MultiByteToWideChar(
2978 CP_UTF8, 0, s, len,
2979 const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
2980 if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
2981 return ws;
2982}
2983#endif
2984
2985struct FileStat {
2986 FileStat(const std::string &path);
2987 bool is_file() const;
2988 bool is_dir() const;
2989 time_t mtime() const;
2990 size_t size() const;
2991
2992private:
2993#if defined(_WIN32)
2994 struct _stat st_;
2995#else
2996 struct stat st_;
2997#endif
2998 int ret_ = -1;
2999};
3000
3001std::string make_host_and_port_string(const std::string &host, int port,
3002 bool is_ssl);
3003
3004std::string trim_copy(const std::string &s);
3005
3006void divide(
3007 const char *data, std::size_t size, char d,
3008 std::function<void(const char *, std::size_t, const char *, std::size_t)>
3009 fn);
3010
3011void divide(
3012 const std::string &str, char d,
3013 std::function<void(const char *, std::size_t, const char *, std::size_t)>
3014 fn);
3015
3016void split(const char *b, const char *e, char d,
3017 std::function<void(const char *, const char *)> fn);
3018
3019void split(const char *b, const char *e, char d, size_t m,
3020 std::function<void(const char *, const char *)> fn);
3021
3023 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
3024 time_t write_timeout_sec, time_t write_timeout_usec,
3025 time_t max_timeout_msec,
3026 std::chrono::time_point<std::chrono::steady_clock> start_time,
3027 std::function<bool(Stream &)> callback);
3028
3029socket_t create_client_socket(const std::string &host, const std::string &ip,
3030 int port, int address_family, bool tcp_nodelay,
3031 bool ipv6_v6only, SocketOptions socket_options,
3032 time_t connection_timeout_sec,
3033 time_t connection_timeout_usec,
3034 time_t read_timeout_sec, time_t read_timeout_usec,
3035 time_t write_timeout_sec,
3036 time_t write_timeout_usec,
3037 const std::string &intf, Error &error);
3038
3039const char *get_header_value(const Headers &headers, const std::string &key,
3040 const char *def, size_t id);
3041
3042std::string params_to_query_str(const Params &params);
3043
3044void parse_query_text(const char *data, std::size_t size, Params &params);
3045
3046void parse_query_text(const std::string &s, Params &params);
3047
3048bool parse_multipart_boundary(const std::string &content_type,
3049 std::string &boundary);
3050
3051bool parse_range_header(const std::string &s, Ranges &ranges);
3052
3053bool parse_accept_header(const std::string &s,
3054 std::vector<std::string> &content_types);
3055
3056int close_socket(socket_t sock);
3057
3058ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);
3059
3060ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);
3061
3062enum class EncodingType { None = 0, Gzip, Brotli, Zstd };
3063
3064EncodingType encoding_type(const Request &req, const Response &res);
3065
3066class BufferStream final : public Stream {
3067public:
3068 BufferStream() = default;
3069 ~BufferStream() override = default;
3070
3071 bool is_readable() const override;
3072 bool wait_readable() const override;
3073 bool wait_writable() const override;
3074 ssize_t read(char *ptr, size_t size) override;
3075 ssize_t write(const char *ptr, size_t size) override;
3076 void get_remote_ip_and_port(std::string &ip, int &port) const override;
3077 void get_local_ip_and_port(std::string &ip, int &port) const override;
3078 socket_t socket() const override;
3079 time_t duration() const override;
3080
3081 const std::string &get_buffer() const;
3082
3083private:
3084 std::string buffer;
3085 size_t position = 0;
3086};
3087
3089public:
3090 virtual ~compressor() = default;
3091
3092 typedef std::function<bool(const char *data, size_t data_len)> Callback;
3093 virtual bool compress(const char *data, size_t data_length, bool last,
3094 Callback callback) = 0;
3095};
3096
3098public:
3099 virtual ~decompressor() = default;
3100
3101 virtual bool is_valid() const = 0;
3102
3103 typedef std::function<bool(const char *data, size_t data_len)> Callback;
3104 virtual bool decompress(const char *data, size_t data_length,
3105 Callback callback) = 0;
3106};
3107
3108class nocompressor final : public compressor {
3109public:
3110 ~nocompressor() override = default;
3111
3112 bool compress(const char *data, size_t data_length, bool /*last*/,
3113 Callback callback) override;
3114};
3115
3116#ifdef CPPHTTPLIB_ZLIB_SUPPORT
3117class gzip_compressor final : public compressor {
3118public:
3119 gzip_compressor();
3120 ~gzip_compressor() override;
3121
3122 bool compress(const char *data, size_t data_length, bool last,
3123 Callback callback) override;
3124
3125private:
3126 bool is_valid_ = false;
3127 z_stream strm_;
3128};
3129
3130class gzip_decompressor final : public decompressor {
3131public:
3132 gzip_decompressor();
3133 ~gzip_decompressor() override;
3134
3135 bool is_valid() const override;
3136
3137 bool decompress(const char *data, size_t data_length,
3138 Callback callback) override;
3139
3140private:
3141 bool is_valid_ = false;
3142 z_stream strm_;
3143};
3144#endif
3145
3146#ifdef CPPHTTPLIB_BROTLI_SUPPORT
3147class brotli_compressor final : public compressor {
3148public:
3149 brotli_compressor();
3150 ~brotli_compressor();
3151
3152 bool compress(const char *data, size_t data_length, bool last,
3153 Callback callback) override;
3154
3155private:
3156 BrotliEncoderState *state_ = nullptr;
3157};
3158
3159class brotli_decompressor final : public decompressor {
3160public:
3161 brotli_decompressor();
3162 ~brotli_decompressor();
3163
3164 bool is_valid() const override;
3165
3166 bool decompress(const char *data, size_t data_length,
3167 Callback callback) override;
3168
3169private:
3170 BrotliDecoderResult decoder_r;
3171 BrotliDecoderState *decoder_s = nullptr;
3172};
3173#endif
3174
3175#ifdef CPPHTTPLIB_ZSTD_SUPPORT
3176class zstd_compressor : public compressor {
3177public:
3178 zstd_compressor();
3179 ~zstd_compressor();
3180
3181 bool compress(const char *data, size_t data_length, bool last,
3182 Callback callback) override;
3183
3184private:
3185 ZSTD_CCtx *ctx_ = nullptr;
3186};
3187
3188class zstd_decompressor : public decompressor {
3189public:
3190 zstd_decompressor();
3191 ~zstd_decompressor();
3192
3193 bool is_valid() const override;
3194
3195 bool decompress(const char *data, size_t data_length,
3196 Callback callback) override;
3197
3198private:
3199 ZSTD_DCtx *ctx_ = nullptr;
3200};
3201#endif
3202
3203// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
3204// to store data. The call can set memory on stack for performance.
3206public:
3207 stream_line_reader(Stream &strm, char *fixed_buffer,
3208 size_t fixed_buffer_size);
3209 const char *ptr() const;
3210 size_t size() const;
3211 bool end_with_crlf() const;
3212 bool getline();
3213
3214private:
3215 void append(char c);
3216
3217 Stream &strm_;
3218 char *fixed_buffer_;
3219 const size_t fixed_buffer_size_;
3220 size_t fixed_buffer_used_size_ = 0;
3221 std::string growable_buffer_;
3222};
3223
3224bool parse_trailers(stream_line_reader &line_reader, Headers &dest,
3225 const Headers &src_headers);
3226
3230 bool finished = false;
3231 char line_buf[64];
3234
3235 explicit ChunkedDecoder(Stream &s);
3236
3237 ssize_t read_payload(char *buf, size_t len, size_t &out_chunk_offset,
3238 size_t &out_chunk_total);
3239
3240 bool parse_trailers_into(Headers &dest, const Headers &src_headers);
3241};
3242
3243class mmap {
3244public:
3245 mmap(const char *path);
3246 ~mmap();
3247
3248 bool open(const char *path);
3249 void close();
3250
3251 bool is_open() const;
3252 size_t size() const;
3253 const char *data() const;
3254
3255private:
3256#if defined(_WIN32)
3257 HANDLE hFile_ = NULL;
3258 HANDLE hMapping_ = NULL;
3259#else
3260 int fd_ = -1;
3261#endif
3262 size_t size_ = 0;
3263 void *addr_ = nullptr;
3264 bool is_open_empty_file = false;
3265};
3266
3267// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
3268namespace fields {
3269
3270bool is_token_char(char c);
3271bool is_token(const std::string &s);
3272bool is_field_name(const std::string &s);
3273bool is_vchar(char c);
3274bool is_obs_text(char c);
3275bool is_field_vchar(char c);
3276bool is_field_content(const std::string &s);
3277bool is_field_value(const std::string &s);
3278
3279} // namespace fields
3280} // namespace detail
3281
3282/*
3283 * TLS Abstraction Layer Declarations
3284 */
3285
3286#ifdef CPPHTTPLIB_SSL_ENABLED
3287// TLS abstraction layer - backend-specific type declarations
3288#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
3289namespace tls {
3290namespace impl {
3291
3292// Mbed TLS context wrapper (holds config, entropy, DRBG, CA chain, own
3293// cert/key). This struct is accessible via tls::impl for use in SSL context
3294// setup callbacks (cast ctx_t to tls::impl::MbedTlsContext*).
3295struct MbedTlsContext {
3296 mbedtls_ssl_config conf;
3297 mbedtls_entropy_context entropy;
3298 mbedtls_ctr_drbg_context ctr_drbg;
3299 mbedtls_x509_crt ca_chain;
3300 mbedtls_x509_crt own_cert;
3301 mbedtls_pk_context own_key;
3302 bool is_server = false;
3303 bool verify_client = false;
3304 bool has_verify_callback = false;
3305
3306 MbedTlsContext();
3307 ~MbedTlsContext();
3308
3309 MbedTlsContext(const MbedTlsContext &) = delete;
3310 MbedTlsContext &operator=(const MbedTlsContext &) = delete;
3311};
3312
3313} // namespace impl
3314} // namespace tls
3315#endif
3316
3317#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
3318namespace tls {
3319namespace impl {
3320
3321// wolfSSL context wrapper (holds WOLFSSL_CTX and related state).
3322// This struct is accessible via tls::impl for use in SSL context
3323// setup callbacks (cast ctx_t to tls::impl::WolfSSLContext*).
3324struct WolfSSLContext {
3325 WOLFSSL_CTX *ctx = nullptr;
3326 bool is_server = false;
3327 bool verify_client = false;
3328 bool has_verify_callback = false;
3329 std::string ca_pem_data_; // accumulated PEM for get_ca_names/get_ca_certs
3330
3331 WolfSSLContext();
3332 ~WolfSSLContext();
3333
3334 WolfSSLContext(const WolfSSLContext &) = delete;
3335 WolfSSLContext &operator=(const WolfSSLContext &) = delete;
3336};
3337
3338// CA store for wolfSSL: holds raw PEM bytes to allow reloading into any ctx
3339struct WolfSSLCAStore {
3340 std::string pem_data;
3341};
3342
3343} // namespace impl
3344} // namespace tls
3345#endif
3346
3347#endif // CPPHTTPLIB_SSL_ENABLED
3348
3349namespace stream {
3350
3351class Result {
3352public:
3353 Result();
3354 explicit Result(ClientImpl::StreamHandle &&handle, size_t chunk_size = 8192);
3355 Result(Result &&other) noexcept;
3356 Result &operator=(Result &&other) noexcept;
3357 Result(const Result &) = delete;
3358 Result &operator=(const Result &) = delete;
3359
3360 // Response info
3361 bool is_valid() const;
3362 explicit operator bool() const;
3363 int status() const;
3364 const Headers &headers() const;
3365 std::string get_header_value(const std::string &key,
3366 const char *def = "") const;
3367 bool has_header(const std::string &key) const;
3368 Error error() const;
3369 Error read_error() const;
3370 bool has_read_error() const;
3371
3372 // Stream reading
3373 bool next();
3374 const char *data() const;
3375 size_t size() const;
3376 std::string read_all();
3377
3378private:
3380 std::string buffer_;
3381 size_t current_size_ = 0;
3382 size_t chunk_size_;
3383 bool finished_ = false;
3384};
3385
3386// GET
3387template <typename ClientType>
3388inline Result Get(ClientType &cli, const std::string &path,
3389 size_t chunk_size = 8192) {
3390 return Result{cli.open_stream("GET", path), chunk_size};
3391}
3392
3393template <typename ClientType>
3394inline Result Get(ClientType &cli, const std::string &path,
3395 const Headers &headers, size_t chunk_size = 8192) {
3396 return Result{cli.open_stream("GET", path, {}, headers), chunk_size};
3397}
3398
3399template <typename ClientType>
3400inline Result Get(ClientType &cli, const std::string &path,
3401 const Params &params, size_t chunk_size = 8192) {
3402 return Result{cli.open_stream("GET", path, params), chunk_size};
3403}
3404
3405template <typename ClientType>
3406inline Result Get(ClientType &cli, const std::string &path,
3407 const Params &params, const Headers &headers,
3408 size_t chunk_size = 8192) {
3409 return Result{cli.open_stream("GET", path, params, headers), chunk_size};
3410}
3411
3412// POST
3413template <typename ClientType>
3414inline Result Post(ClientType &cli, const std::string &path,
3415 const std::string &body, const std::string &content_type,
3416 size_t chunk_size = 8192) {
3417 return Result{cli.open_stream("POST", path, {}, {}, body, content_type),
3418 chunk_size};
3419}
3420
3421template <typename ClientType>
3422inline Result Post(ClientType &cli, const std::string &path,
3423 const Headers &headers, const std::string &body,
3424 const std::string &content_type, size_t chunk_size = 8192) {
3425 return Result{cli.open_stream("POST", path, {}, headers, body, content_type),
3426 chunk_size};
3427}
3428
3429template <typename ClientType>
3430inline Result Post(ClientType &cli, const std::string &path,
3431 const Params &params, const std::string &body,
3432 const std::string &content_type, size_t chunk_size = 8192) {
3433 return Result{cli.open_stream("POST", path, params, {}, body, content_type),
3434 chunk_size};
3435}
3436
3437template <typename ClientType>
3438inline Result Post(ClientType &cli, const std::string &path,
3439 const Params &params, const Headers &headers,
3440 const std::string &body, const std::string &content_type,
3441 size_t chunk_size = 8192) {
3442 return Result{
3443 cli.open_stream("POST", path, params, headers, body, content_type),
3444 chunk_size};
3445}
3446
3447// PUT
3448template <typename ClientType>
3449inline Result Put(ClientType &cli, const std::string &path,
3450 const std::string &body, const std::string &content_type,
3451 size_t chunk_size = 8192) {
3452 return Result{cli.open_stream("PUT", path, {}, {}, body, content_type),
3453 chunk_size};
3454}
3455
3456template <typename ClientType>
3457inline Result Put(ClientType &cli, const std::string &path,
3458 const Headers &headers, const std::string &body,
3459 const std::string &content_type, size_t chunk_size = 8192) {
3460 return Result{cli.open_stream("PUT", path, {}, headers, body, content_type),
3461 chunk_size};
3462}
3463
3464template <typename ClientType>
3465inline Result Put(ClientType &cli, const std::string &path,
3466 const Params &params, const std::string &body,
3467 const std::string &content_type, size_t chunk_size = 8192) {
3468 return Result{cli.open_stream("PUT", path, params, {}, body, content_type),
3469 chunk_size};
3470}
3471
3472template <typename ClientType>
3473inline Result Put(ClientType &cli, const std::string &path,
3474 const Params &params, const Headers &headers,
3475 const std::string &body, const std::string &content_type,
3476 size_t chunk_size = 8192) {
3477 return Result{
3478 cli.open_stream("PUT", path, params, headers, body, content_type),
3479 chunk_size};
3480}
3481
3482// PATCH
3483template <typename ClientType>
3484inline Result Patch(ClientType &cli, const std::string &path,
3485 const std::string &body, const std::string &content_type,
3486 size_t chunk_size = 8192) {
3487 return Result{cli.open_stream("PATCH", path, {}, {}, body, content_type),
3488 chunk_size};
3489}
3490
3491template <typename ClientType>
3492inline Result Patch(ClientType &cli, const std::string &path,
3493 const Headers &headers, const std::string &body,
3494 const std::string &content_type, size_t chunk_size = 8192) {
3495 return Result{cli.open_stream("PATCH", path, {}, headers, body, content_type),
3496 chunk_size};
3497}
3498
3499template <typename ClientType>
3500inline Result Patch(ClientType &cli, const std::string &path,
3501 const Params &params, const std::string &body,
3502 const std::string &content_type, size_t chunk_size = 8192) {
3503 return Result{cli.open_stream("PATCH", path, params, {}, body, content_type),
3504 chunk_size};
3505}
3506
3507template <typename ClientType>
3508inline Result Patch(ClientType &cli, const std::string &path,
3509 const Params &params, const Headers &headers,
3510 const std::string &body, const std::string &content_type,
3511 size_t chunk_size = 8192) {
3512 return Result{
3513 cli.open_stream("PATCH", path, params, headers, body, content_type),
3514 chunk_size};
3515}
3516
3517// DELETE
3518template <typename ClientType>
3519inline Result Delete(ClientType &cli, const std::string &path,
3520 size_t chunk_size = 8192) {
3521 return Result{cli.open_stream("DELETE", path), chunk_size};
3522}
3523
3524template <typename ClientType>
3525inline Result Delete(ClientType &cli, const std::string &path,
3526 const Headers &headers, size_t chunk_size = 8192) {
3527 return Result{cli.open_stream("DELETE", path, {}, headers), chunk_size};
3528}
3529
3530template <typename ClientType>
3531inline Result Delete(ClientType &cli, const std::string &path,
3532 const std::string &body, const std::string &content_type,
3533 size_t chunk_size = 8192) {
3534 return Result{cli.open_stream("DELETE", path, {}, {}, body, content_type),
3535 chunk_size};
3536}
3537
3538template <typename ClientType>
3539inline Result Delete(ClientType &cli, const std::string &path,
3540 const Headers &headers, const std::string &body,
3541 const std::string &content_type,
3542 size_t chunk_size = 8192) {
3543 return Result{
3544 cli.open_stream("DELETE", path, {}, headers, body, content_type),
3545 chunk_size};
3546}
3547
3548template <typename ClientType>
3549inline Result Delete(ClientType &cli, const std::string &path,
3550 const Params &params, size_t chunk_size = 8192) {
3551 return Result{cli.open_stream("DELETE", path, params), chunk_size};
3552}
3553
3554template <typename ClientType>
3555inline Result Delete(ClientType &cli, const std::string &path,
3556 const Params &params, const Headers &headers,
3557 size_t chunk_size = 8192) {
3558 return Result{cli.open_stream("DELETE", path, params, headers), chunk_size};
3559}
3560
3561template <typename ClientType>
3562inline Result Delete(ClientType &cli, const std::string &path,
3563 const Params &params, const std::string &body,
3564 const std::string &content_type,
3565 size_t chunk_size = 8192) {
3566 return Result{cli.open_stream("DELETE", path, params, {}, body, content_type),
3567 chunk_size};
3568}
3569
3570template <typename ClientType>
3571inline Result Delete(ClientType &cli, const std::string &path,
3572 const Params &params, const Headers &headers,
3573 const std::string &body, const std::string &content_type,
3574 size_t chunk_size = 8192) {
3575 return Result{
3576 cli.open_stream("DELETE", path, params, headers, body, content_type),
3577 chunk_size};
3578}
3579
3580// HEAD
3581template <typename ClientType>
3582inline Result Head(ClientType &cli, const std::string &path,
3583 size_t chunk_size = 8192) {
3584 return Result{cli.open_stream("HEAD", path), chunk_size};
3585}
3586
3587template <typename ClientType>
3588inline Result Head(ClientType &cli, const std::string &path,
3589 const Headers &headers, size_t chunk_size = 8192) {
3590 return Result{cli.open_stream("HEAD", path, {}, headers), chunk_size};
3591}
3592
3593template <typename ClientType>
3594inline Result Head(ClientType &cli, const std::string &path,
3595 const Params &params, size_t chunk_size = 8192) {
3596 return Result{cli.open_stream("HEAD", path, params), chunk_size};
3597}
3598
3599template <typename ClientType>
3600inline Result Head(ClientType &cli, const std::string &path,
3601 const Params &params, const Headers &headers,
3602 size_t chunk_size = 8192) {
3603 return Result{cli.open_stream("HEAD", path, params, headers), chunk_size};
3604}
3605
3606// OPTIONS
3607template <typename ClientType>
3608inline Result Options(ClientType &cli, const std::string &path,
3609 size_t chunk_size = 8192) {
3610 return Result{cli.open_stream("OPTIONS", path), chunk_size};
3611}
3612
3613template <typename ClientType>
3614inline Result Options(ClientType &cli, const std::string &path,
3615 const Headers &headers, size_t chunk_size = 8192) {
3616 return Result{cli.open_stream("OPTIONS", path, {}, headers), chunk_size};
3617}
3618
3619template <typename ClientType>
3620inline Result Options(ClientType &cli, const std::string &path,
3621 const Params &params, size_t chunk_size = 8192) {
3622 return Result{cli.open_stream("OPTIONS", path, params), chunk_size};
3623}
3624
3625template <typename ClientType>
3626inline Result Options(ClientType &cli, const std::string &path,
3627 const Params &params, const Headers &headers,
3628 size_t chunk_size = 8192) {
3629 return Result{cli.open_stream("OPTIONS", path, params, headers), chunk_size};
3630}
3631
3632} // namespace stream
3633
3634namespace sse {
3635
3637 std::string event; // Event type (default: "message")
3638 std::string data; // Event payload
3639 std::string id; // Event ID for Last-Event-ID header
3640
3641 SSEMessage();
3642 void clear();
3643};
3644
3646public:
3647 using MessageHandler = std::function<void(const SSEMessage &)>;
3648 using ErrorHandler = std::function<void(Error)>;
3649 using OpenHandler = std::function<void()>;
3650
3651 SSEClient(Client &client, const std::string &path);
3652 SSEClient(Client &client, const std::string &path, const Headers &headers);
3653 ~SSEClient();
3654
3655 SSEClient(const SSEClient &) = delete;
3656 SSEClient &operator=(const SSEClient &) = delete;
3657
3658 // Event handlers
3660 SSEClient &on_event(const std::string &type, MessageHandler handler);
3661 SSEClient &on_open(OpenHandler handler);
3665
3666 // Update headers (thread-safe)
3667 SSEClient &set_headers(const Headers &headers);
3668
3669 // State accessors
3670 bool is_connected() const;
3671 const std::string &last_event_id() const;
3672
3673 // Blocking start - runs event loop with auto-reconnect
3674 void start();
3675
3676 // Non-blocking start - runs in background thread
3677 void start_async();
3678
3679 // Stop the client (thread-safe)
3680 void stop();
3681
3682private:
3683 bool parse_sse_line(const std::string &line, SSEMessage &msg, int &retry_ms);
3684 void run_event_loop();
3685 void dispatch_event(const SSEMessage &msg);
3686 bool should_reconnect(int count) const;
3687 void wait_for_reconnect();
3688
3689 // Client and path
3690 Client &client_;
3691 std::string path_;
3692 Headers headers_;
3693 mutable std::mutex headers_mutex_;
3694
3695 // Callbacks
3696 MessageHandler on_message_;
3697 std::map<std::string, MessageHandler> event_handlers_;
3698 OpenHandler on_open_;
3699 ErrorHandler on_error_;
3700
3701 // Configuration
3702 int reconnect_interval_ms_ = 3000;
3703 int max_reconnect_attempts_ = 0; // 0 = unlimited
3704
3705 // State
3706 std::atomic<bool> running_{false};
3707 std::atomic<bool> connected_{false};
3708 std::string last_event_id_;
3709
3710 // Async support
3711 std::thread async_thread_;
3712};
3713
3714} // namespace sse
3715
3716namespace ws {
3717
3718enum class Opcode : uint8_t {
3720 Text = 0x1,
3721 Binary = 0x2,
3722 Close = 0x8,
3723 Ping = 0x9,
3724 Pong = 0xA,
3725};
3726
3740
3741enum ReadResult : int { Fail = 0, Text = 1, Binary = 2 };
3742
3744public:
3745 WebSocket(const WebSocket &) = delete;
3746 WebSocket &operator=(const WebSocket &) = delete;
3747 ~WebSocket();
3748
3749 ReadResult read(std::string &msg);
3750 bool send(const std::string &data);
3751 bool send(const char *data, size_t len);
3753 const std::string &reason = "");
3754 const Request &request() const;
3755 bool is_open() const;
3756
3757private:
3758 friend class httplib::Server;
3759 friend class WebSocketClient;
3760
3761 WebSocket(
3762 Stream &strm, const Request &req, bool is_server,
3763 time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND)
3764 : strm_(strm), req_(req), is_server_(is_server),
3765 ping_interval_sec_(ping_interval_sec) {
3766 start_heartbeat();
3767 }
3768
3769 WebSocket(
3770 std::unique_ptr<Stream> &&owned_strm, const Request &req, bool is_server,
3771 time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND)
3772 : strm_(*owned_strm), owned_strm_(std::move(owned_strm)), req_(req),
3773 is_server_(is_server), ping_interval_sec_(ping_interval_sec) {
3774 start_heartbeat();
3775 }
3776
3777 void start_heartbeat();
3778 bool send_frame(Opcode op, const char *data, size_t len, bool fin = true);
3779
3780 Stream &strm_;
3781 std::unique_ptr<Stream> owned_strm_;
3782 Request req_;
3783 bool is_server_;
3784 time_t ping_interval_sec_;
3785 std::atomic<bool> closed_{false};
3786 std::mutex write_mutex_;
3787 std::thread ping_thread_;
3788 std::mutex ping_mutex_;
3789 std::condition_variable ping_cv_;
3790};
3791
3793public:
3794 explicit WebSocketClient(const std::string &scheme_host_port_path,
3795 const Headers &headers = {});
3796
3800
3801 bool is_valid() const;
3802
3803 bool connect();
3804 ReadResult read(std::string &msg);
3805 bool send(const std::string &data);
3806 bool send(const char *data, size_t len);
3808 const std::string &reason = "");
3809 bool is_open() const;
3810 const std::string &subprotocol() const;
3811 void set_read_timeout(time_t sec, time_t usec = 0);
3812 void set_write_timeout(time_t sec, time_t usec = 0);
3813 void set_websocket_ping_interval(time_t sec);
3814 void set_tcp_nodelay(bool on);
3815 void set_address_family(int family);
3816 void set_ipv6_v6only(bool on);
3817 void set_socket_options(SocketOptions socket_options);
3818 void set_connection_timeout(time_t sec, time_t usec = 0);
3819 void set_interface(const std::string &intf);
3820
3821#ifdef CPPHTTPLIB_SSL_ENABLED
3822 void set_ca_cert_path(const std::string &path);
3823 void set_ca_cert_store(tls::ca_store_t store);
3824 void enable_server_certificate_verification(bool enabled);
3825#endif
3826
3827private:
3828 void shutdown_and_close();
3829 bool create_stream(std::unique_ptr<Stream> &strm);
3830
3831 std::string host_;
3832 int port_;
3833 std::string path_;
3834 Headers headers_;
3835 std::string subprotocol_;
3836 bool is_valid_ = false;
3837 socket_t sock_ = INVALID_SOCKET;
3838 std::unique_ptr<WebSocket> ws_;
3839 time_t read_timeout_sec_ = CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND;
3840 time_t read_timeout_usec_ = 0;
3841 time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
3842 time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
3843 time_t websocket_ping_interval_sec_ =
3845 int address_family_ = AF_UNSPEC;
3846 bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
3847 bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
3848 SocketOptions socket_options_ = nullptr;
3849 time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
3850 time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
3851 std::string interface_;
3852
3853#ifdef CPPHTTPLIB_SSL_ENABLED
3854 bool is_ssl_ = false;
3855 tls::ctx_t tls_ctx_ = nullptr;
3856 tls::session_t tls_session_ = nullptr;
3857 std::string ca_cert_file_path_;
3858 tls::ca_store_t ca_cert_store_ = nullptr;
3859 bool server_certificate_verification_ = true;
3860#endif
3861};
3862
3863namespace impl {
3864
3865bool is_valid_utf8(const std::string &s);
3866
3867bool read_websocket_frame(Stream &strm, Opcode &opcode, std::string &payload,
3868 bool &fin, bool expect_masked, size_t max_len);
3869
3870} // namespace impl
3871
3872} // namespace ws
3873
3874// ----------------------------------------------------------------------------
3875
3876/*
3877 * Implementation that will be part of the .cc file if split into .h + .cc.
3878 */
3879
3880namespace stream {
3881
3882// stream::Result implementations
3883inline Result::Result() : chunk_size_(8192) {}
3884
3885inline Result::Result(ClientImpl::StreamHandle &&handle, size_t chunk_size)
3886 : handle_(std::move(handle)), chunk_size_(chunk_size) {}
3887
3888inline Result::Result(Result &&other) noexcept
3889 : handle_(std::move(other.handle_)), buffer_(std::move(other.buffer_)),
3890 current_size_(other.current_size_), chunk_size_(other.chunk_size_),
3891 finished_(other.finished_) {
3892 other.current_size_ = 0;
3893 other.finished_ = true;
3894}
3895
3896inline Result &Result::operator=(Result &&other) noexcept {
3897 if (this != &other) {
3898 handle_ = std::move(other.handle_);
3899 buffer_ = std::move(other.buffer_);
3900 current_size_ = other.current_size_;
3901 chunk_size_ = other.chunk_size_;
3902 finished_ = other.finished_;
3903 other.current_size_ = 0;
3904 other.finished_ = true;
3905 }
3906 return *this;
3907}
3908
3909inline bool Result::is_valid() const { return handle_.is_valid(); }
3910inline Result::operator bool() const { return is_valid(); }
3911
3912inline int Result::status() const {
3913 return handle_.response ? handle_.response->status : -1;
3914}
3915
3916inline const Headers &Result::headers() const {
3917 static const Headers empty_headers;
3918 return handle_.response ? handle_.response->headers : empty_headers;
3919}
3920
3921inline std::string Result::get_header_value(const std::string &key,
3922 const char *def) const {
3923 return handle_.response ? handle_.response->get_header_value(key, def) : def;
3924}
3925
3926inline bool Result::has_header(const std::string &key) const {
3927 return handle_.response ? handle_.response->has_header(key) : false;
3928}
3929
3930inline Error Result::error() const { return handle_.error; }
3931inline Error Result::read_error() const { return handle_.get_read_error(); }
3932inline bool Result::has_read_error() const { return handle_.has_read_error(); }
3933
3934inline bool Result::next() {
3935 if (!handle_.is_valid() || finished_) { return false; }
3936
3937 if (buffer_.size() < chunk_size_) { buffer_.resize(chunk_size_); }
3938
3939 ssize_t n = handle_.read(&buffer_[0], chunk_size_);
3940 if (n > 0) {
3941 current_size_ = static_cast<size_t>(n);
3942 return true;
3943 }
3944
3945 current_size_ = 0;
3946 finished_ = true;
3947 return false;
3948}
3949
3950inline const char *Result::data() const { return buffer_.data(); }
3951inline size_t Result::size() const { return current_size_; }
3952
3953inline std::string Result::read_all() {
3954 std::string result;
3955 while (next()) {
3956 result.append(data(), size());
3957 }
3958 return result;
3959}
3960
3961} // namespace stream
3962
3963namespace sse {
3964
3965// SSEMessage implementations
3966inline SSEMessage::SSEMessage() : event("message") {}
3967
3968inline void SSEMessage::clear() {
3969 event = "message";
3970 data.clear();
3971 id.clear();
3972}
3973
3974// SSEClient implementations
3975inline SSEClient::SSEClient(Client &client, const std::string &path)
3976 : client_(client), path_(path) {}
3977
3978inline SSEClient::SSEClient(Client &client, const std::string &path,
3979 const Headers &headers)
3980 : client_(client), path_(path), headers_(headers) {}
3981
3983
3985 on_message_ = std::move(handler);
3986 return *this;
3987}
3988
3989inline SSEClient &SSEClient::on_event(const std::string &type,
3990 MessageHandler handler) {
3991 event_handlers_[type] = std::move(handler);
3992 return *this;
3993}
3994
3996 on_open_ = std::move(handler);
3997 return *this;
3998}
3999
4001 on_error_ = std::move(handler);
4002 return *this;
4003}
4004
4006 reconnect_interval_ms_ = ms;
4007 return *this;
4008}
4009
4011 max_reconnect_attempts_ = n;
4012 return *this;
4013}
4014
4016 std::lock_guard<std::mutex> lock(headers_mutex_);
4017 headers_ = headers;
4018 return *this;
4019}
4020
4021inline bool SSEClient::is_connected() const { return connected_.load(); }
4022
4023inline const std::string &SSEClient::last_event_id() const {
4024 return last_event_id_;
4025}
4026
4027inline void SSEClient::start() {
4028 running_.store(true);
4029 run_event_loop();
4030}
4031
4033 running_.store(true);
4034 async_thread_ = std::thread([this]() { run_event_loop(); });
4035}
4036
4037inline void SSEClient::stop() {
4038 running_.store(false);
4039 client_.stop(); // Cancel any pending operations
4040 if (async_thread_.joinable()) { async_thread_.join(); }
4041}
4042
4043inline bool SSEClient::parse_sse_line(const std::string &line, SSEMessage &msg,
4044 int &retry_ms) {
4045 // Blank line signals end of event
4046 if (line.empty() || line == "\r") { return true; }
4047
4048 // Lines starting with ':' are comments (ignored)
4049 if (!line.empty() && line[0] == ':') { return false; }
4050
4051 // Find the colon separator
4052 auto colon_pos = line.find(':');
4053 if (colon_pos == std::string::npos) {
4054 // Line with no colon is treated as field name with empty value
4055 return false;
4056 }
4057
4058 auto field = line.substr(0, colon_pos);
4059 std::string value;
4060
4061 // Value starts after colon, skip optional single space
4062 if (colon_pos + 1 < line.size()) {
4063 auto value_start = colon_pos + 1;
4064 if (line[value_start] == ' ') { value_start++; }
4065 value = line.substr(value_start);
4066 // Remove trailing \r if present
4067 if (!value.empty() && value.back() == '\r') { value.pop_back(); }
4068 }
4069
4070 // Handle known fields
4071 if (field == "event") {
4072 msg.event = value;
4073 } else if (field == "data") {
4074 // Multiple data lines are concatenated with newlines
4075 if (!msg.data.empty()) { msg.data += "\n"; }
4076 msg.data += value;
4077 } else if (field == "id") {
4078 // Empty id is valid (clears the last event ID)
4079 msg.id = value;
4080 } else if (field == "retry") {
4081 // Parse retry interval in milliseconds
4082 {
4083 int v = 0;
4084 auto res =
4085 detail::from_chars(value.data(), value.data() + value.size(), v);
4086 if (res.ec == std::errc{}) { retry_ms = v; }
4087 }
4088 }
4089 // Unknown fields are ignored per SSE spec
4090
4091 return false;
4092}
4093
4094inline void SSEClient::run_event_loop() {
4095 auto reconnect_count = 0;
4096
4097 while (running_.load()) {
4098 // Build headers, including Last-Event-ID if we have one
4099 Headers request_headers;
4100 {
4101 std::lock_guard<std::mutex> lock(headers_mutex_);
4102 request_headers = headers_;
4103 }
4104 if (!last_event_id_.empty()) {
4105 request_headers.emplace("Last-Event-ID", last_event_id_);
4106 }
4107
4108 // Open streaming connection
4109 auto result = stream::Get(client_, path_, request_headers);
4110
4111 // Connection error handling
4112 if (!result) {
4113 connected_.store(false);
4114 if (on_error_) { on_error_(result.error()); }
4115
4116 if (!should_reconnect(reconnect_count)) { break; }
4117 wait_for_reconnect();
4118 reconnect_count++;
4119 continue;
4120 }
4121
4122 if (result.status() != StatusCode::OK_200) {
4123 connected_.store(false);
4124 if (on_error_) { on_error_(Error::Connection); }
4125
4126 // For certain errors, don't reconnect.
4127 // Note: 401 is intentionally absent so that handlers can refresh
4128 // credentials via set_headers() and let the client reconnect.
4129 if (result.status() == StatusCode::NoContent_204 ||
4130 result.status() == StatusCode::NotFound_404 ||
4131 result.status() == StatusCode::Forbidden_403) {
4132 break;
4133 }
4134
4135 if (!should_reconnect(reconnect_count)) { break; }
4136 wait_for_reconnect();
4137 reconnect_count++;
4138 continue;
4139 }
4140
4141 // Connection successful
4142 connected_.store(true);
4143 reconnect_count = 0;
4144 if (on_open_) { on_open_(); }
4145
4146 // Event receiving loop
4147 std::string buffer;
4148 SSEMessage current_msg;
4149
4150 while (running_.load() && result.next()) {
4151 buffer.append(result.data(), result.size());
4152
4153 // Process complete lines in the buffer
4154 size_t line_start = 0;
4155 size_t newline_pos;
4156
4157 while ((newline_pos = buffer.find('\n', line_start)) !=
4158 std::string::npos) {
4159 auto line = buffer.substr(line_start, newline_pos - line_start);
4160 line_start = newline_pos + 1;
4161
4162 // Parse the line and check if event is complete
4163 auto event_complete =
4164 parse_sse_line(line, current_msg, reconnect_interval_ms_);
4165
4166 if (event_complete && !current_msg.data.empty()) {
4167 // Update last_event_id for reconnection
4168 if (!current_msg.id.empty()) { last_event_id_ = current_msg.id; }
4169
4170 // Dispatch event to appropriate handler
4171 dispatch_event(current_msg);
4172
4173 current_msg.clear();
4174 }
4175 }
4176
4177 // Keep unprocessed data in buffer
4178 buffer.erase(0, line_start);
4179 }
4180
4181 // Connection ended
4182 connected_.store(false);
4183
4184 if (!running_.load()) { break; }
4185
4186 // Check for read errors
4187 if (result.has_read_error()) {
4188 if (on_error_) { on_error_(result.read_error()); }
4189 }
4190
4191 if (!should_reconnect(reconnect_count)) { break; }
4192 wait_for_reconnect();
4193 reconnect_count++;
4194 }
4195
4196 connected_.store(false);
4197}
4198
4199inline void SSEClient::dispatch_event(const SSEMessage &msg) {
4200 // Check for specific event type handler first
4201 auto it = event_handlers_.find(msg.event);
4202 if (it != event_handlers_.end()) {
4203 it->second(msg);
4204 return;
4205 }
4206
4207 // Fall back to generic message handler
4208 if (on_message_) { on_message_(msg); }
4209}
4210
4211inline bool SSEClient::should_reconnect(int count) const {
4212 if (!running_.load()) { return false; }
4213 if (max_reconnect_attempts_ == 0) { return true; } // unlimited
4214 return count < max_reconnect_attempts_;
4215}
4216
4217inline void SSEClient::wait_for_reconnect() {
4218 // Use small increments to check running_ flag frequently
4219 auto waited = 0;
4220 while (running_.load() && waited < reconnect_interval_ms_) {
4221 std::this_thread::sleep_for(std::chrono::milliseconds(100));
4222 waited += 100;
4223 }
4224}
4225
4226} // namespace sse
4227
4228#ifdef CPPHTTPLIB_SSL_ENABLED
4229/*
4230 * TLS abstraction layer - internal function declarations
4231 * These are implementation details and not part of the public API.
4232 */
4233namespace tls {
4234
4235// Client context
4236ctx_t create_client_context();
4237void free_context(ctx_t ctx);
4238bool set_min_version(ctx_t ctx, Version version);
4239bool load_ca_pem(ctx_t ctx, const char *pem, size_t len);
4240bool load_ca_file(ctx_t ctx, const char *file_path);
4241bool load_ca_dir(ctx_t ctx, const char *dir_path);
4242bool load_system_certs(ctx_t ctx);
4243bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
4244 const char *password);
4245bool set_client_cert_file(ctx_t ctx, const char *cert_path,
4246 const char *key_path, const char *password);
4247
4248// Server context
4249ctx_t create_server_context();
4250bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key,
4251 const char *password);
4252bool set_server_cert_file(ctx_t ctx, const char *cert_path,
4253 const char *key_path, const char *password);
4254bool set_client_ca_file(ctx_t ctx, const char *ca_file, const char *ca_dir);
4255void set_verify_client(ctx_t ctx, bool require);
4256
4257// Session management
4258session_t create_session(ctx_t ctx, socket_t sock);
4259void free_session(session_t session);
4260bool set_sni(session_t session, const char *hostname);
4261bool set_hostname(session_t session, const char *hostname);
4262
4263// Handshake (non-blocking capable)
4264TlsError connect(session_t session);
4265TlsError accept(session_t session);
4266
4267// Handshake with timeout (blocking until timeout)
4268bool connect_nonblocking(session_t session, socket_t sock, time_t timeout_sec,
4269 time_t timeout_usec, TlsError *err);
4270bool accept_nonblocking(session_t session, socket_t sock, time_t timeout_sec,
4271 time_t timeout_usec, TlsError *err);
4272
4273// I/O (non-blocking capable)
4274ssize_t read(session_t session, void *buf, size_t len, TlsError &err);
4275ssize_t write(session_t session, const void *buf, size_t len, TlsError &err);
4276int pending(const_session_t session);
4277void shutdown(session_t session, bool graceful);
4278
4279// Connection state
4280bool is_peer_closed(session_t session, socket_t sock);
4281
4282// Certificate verification
4283cert_t get_peer_cert(const_session_t session);
4284void free_cert(cert_t cert);
4285bool verify_hostname(cert_t cert, const char *hostname);
4286uint64_t hostname_mismatch_code();
4287long get_verify_result(const_session_t session);
4288
4289// Certificate introspection
4290std::string get_cert_subject_cn(cert_t cert);
4291std::string get_cert_issuer_name(cert_t cert);
4292bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans);
4293bool get_cert_validity(cert_t cert, time_t &not_before, time_t &not_after);
4294std::string get_cert_serial(cert_t cert);
4295bool get_cert_der(cert_t cert, std::vector<unsigned char> &der);
4296const char *get_sni(const_session_t session);
4297
4298// CA store management
4299ca_store_t create_ca_store(const char *pem, size_t len);
4300void free_ca_store(ca_store_t store);
4301bool set_ca_store(ctx_t ctx, ca_store_t store);
4302size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs);
4303std::vector<std::string> get_ca_names(ctx_t ctx);
4304
4305// Dynamic certificate update (for servers)
4306bool update_server_cert(ctx_t ctx, const char *cert_pem, const char *key_pem,
4307 const char *password);
4308bool update_server_client_ca(ctx_t ctx, const char *ca_pem);
4309
4310// Certificate verification callback
4311bool set_verify_callback(ctx_t ctx, VerifyCallback callback);
4312long get_verify_error(const_session_t session);
4313std::string verify_error_string(long error_code);
4314
4315// TlsError information
4316uint64_t peek_error();
4317uint64_t get_error();
4318std::string error_string(uint64_t code);
4319
4320} // namespace tls
4321#endif // CPPHTTPLIB_SSL_ENABLED
4322
4323/*
4324 * Group 1: detail namespace - Non-SSL utilities
4325 */
4326
4327namespace detail {
4328
4329inline bool set_socket_opt_impl(socket_t sock, int level, int optname,
4330 const void *optval, socklen_t optlen) {
4331 return setsockopt(sock, level, optname,
4332#ifdef _WIN32
4333 reinterpret_cast<const char *>(optval),
4334#else
4335 optval,
4336#endif
4337 optlen) == 0;
4338}
4339
4340inline bool set_socket_opt(socket_t sock, int level, int optname, int optval) {
4341 return set_socket_opt_impl(sock, level, optname, &optval, sizeof(optval));
4342}
4343
4344inline bool set_socket_opt_time(socket_t sock, int level, int optname,
4345 time_t sec, time_t usec) {
4346#ifdef _WIN32
4347 auto timeout = static_cast<uint32_t>(sec * 1000 + usec / 1000);
4348#else
4349 timeval timeout;
4350 timeout.tv_sec = static_cast<long>(sec);
4351 timeout.tv_usec = static_cast<decltype(timeout.tv_usec)>(usec);
4352#endif
4353 return set_socket_opt_impl(sock, level, optname, &timeout, sizeof(timeout));
4354}
4355
4356inline bool is_hex(char c, int &v) {
4357 if (isdigit(c)) {
4358 v = c - '0';
4359 return true;
4360 } else if ('A' <= c && c <= 'F') {
4361 v = c - 'A' + 10;
4362 return true;
4363 } else if ('a' <= c && c <= 'f') {
4364 v = c - 'a' + 10;
4365 return true;
4366 }
4367 return false;
4368}
4369
4370inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
4371 int &val) {
4372 if (i >= s.size()) { return false; }
4373
4374 val = 0;
4375 for (; cnt; i++, cnt--) {
4376 if (!s[i]) { return false; }
4377 auto v = 0;
4378 if (is_hex(s[i], v)) {
4379 val = val * 16 + v;
4380 } else {
4381 return false;
4382 }
4383 }
4384 return true;
4385}
4386
4387inline std::string from_i_to_hex(size_t n) {
4388 static const auto charset = "0123456789abcdef";
4389 std::string ret;
4390 do {
4391 ret = charset[n & 15] + ret;
4392 n >>= 4;
4393 } while (n > 0);
4394 return ret;
4395}
4396
4397inline std::string compute_etag(const FileStat &fs) {
4398 if (!fs.is_file()) { return std::string(); }
4399
4400 // If mtime cannot be determined (negative value indicates an error
4401 // or sentinel), do not generate an ETag. Returning a neutral / fixed
4402 // value like 0 could collide with a real file that legitimately has
4403 // mtime == 0 (epoch) and lead to misleading validators.
4404 auto mtime_raw = fs.mtime();
4405 if (mtime_raw < 0) { return std::string(); }
4406
4407 auto mtime = static_cast<size_t>(mtime_raw);
4408 auto size = fs.size();
4409
4410 return std::string("W/\"") + from_i_to_hex(mtime) + "-" +
4411 from_i_to_hex(size) + "\"";
4412}
4413
4414// Format time_t as HTTP-date (RFC 9110 Section 5.6.7): "Sun, 06 Nov 1994
4415// 08:49:37 GMT" This implementation is defensive: it validates `mtime`, checks
4416// return values from `gmtime_r`/`gmtime_s`, and ensures `strftime` succeeds.
4417inline std::string file_mtime_to_http_date(time_t mtime) {
4418 if (mtime < 0) { return std::string(); }
4419
4420 struct tm tm_buf;
4421#ifdef _WIN32
4422 if (gmtime_s(&tm_buf, &mtime) != 0) { return std::string(); }
4423#else
4424 if (gmtime_r(&mtime, &tm_buf) == nullptr) { return std::string(); }
4425#endif
4426 char buf[64];
4427 if (strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", &tm_buf) == 0) {
4428 return std::string();
4429 }
4430
4431 return std::string(buf);
4432}
4433
4434// Parse HTTP-date (RFC 9110 Section 5.6.7) to time_t. Returns -1 on failure.
4435inline time_t parse_http_date(const std::string &date_str) {
4436 struct tm tm_buf;
4437
4438 // Create a classic locale object once for all parsing attempts
4439 const std::locale classic_locale = std::locale::classic();
4440
4441 // Try to parse using std::get_time (C++11, cross-platform)
4442 auto try_parse = [&](const char *fmt) -> bool {
4443 std::istringstream ss(date_str);
4444 ss.imbue(classic_locale);
4445
4446 memset(&tm_buf, 0, sizeof(tm_buf));
4447 ss >> std::get_time(&tm_buf, fmt);
4448
4449 return !ss.fail();
4450 };
4451
4452 // RFC 9110 preferred format (HTTP-date): "Sun, 06 Nov 1994 08:49:37 GMT"
4453 if (!try_parse("%a, %d %b %Y %H:%M:%S")) {
4454 // RFC 850 format: "Sunday, 06-Nov-94 08:49:37 GMT"
4455 if (!try_parse("%A, %d-%b-%y %H:%M:%S")) {
4456 // asctime format: "Sun Nov 6 08:49:37 1994"
4457 if (!try_parse("%a %b %d %H:%M:%S %Y")) {
4458 return static_cast<time_t>(-1);
4459 }
4460 }
4461 }
4462
4463#ifdef _WIN32
4464 return _mkgmtime(&tm_buf);
4465#elif defined _AIX
4466 return mktime(&tm_buf);
4467#else
4468 return timegm(&tm_buf);
4469#endif
4470}
4471
4472inline bool is_weak_etag(const std::string &s) {
4473 // Check if the string is a weak ETag (starts with 'W/"')
4474 return s.size() > 3 && s[0] == 'W' && s[1] == '/' && s[2] == '"';
4475}
4476
4477inline bool is_strong_etag(const std::string &s) {
4478 // Check if the string is a strong ETag (starts and ends with '"', at least 2
4479 // chars)
4480 return s.size() >= 2 && s[0] == '"' && s.back() == '"';
4481}
4482
4483inline size_t to_utf8(int code, char *buff) {
4484 if (code < 0x0080) {
4485 buff[0] = static_cast<char>(code & 0x7F);
4486 return 1;
4487 } else if (code < 0x0800) {
4488 buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
4489 buff[1] = static_cast<char>(0x80 | (code & 0x3F));
4490 return 2;
4491 } else if (code < 0xD800) {
4492 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
4493 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4494 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
4495 return 3;
4496 } else if (code < 0xE000) { // D800 - DFFF is invalid...
4497 return 0;
4498 } else if (code < 0x10000) {
4499 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
4500 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4501 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
4502 return 3;
4503 } else if (code < 0x110000) {
4504 buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
4505 buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
4506 buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4507 buff[3] = static_cast<char>(0x80 | (code & 0x3F));
4508 return 4;
4509 }
4510
4511 // NOTREACHED
4512 return 0;
4513}
4514
4515} // namespace detail
4516
4517namespace ws {
4518namespace impl {
4519
4520inline bool is_valid_utf8(const std::string &s) {
4521 size_t i = 0;
4522 auto n = s.size();
4523 while (i < n) {
4524 auto c = static_cast<unsigned char>(s[i]);
4525 size_t len;
4526 uint32_t cp;
4527 if (c < 0x80) {
4528 i++;
4529 continue;
4530 } else if ((c & 0xE0) == 0xC0) {
4531 len = 2;
4532 cp = c & 0x1F;
4533 } else if ((c & 0xF0) == 0xE0) {
4534 len = 3;
4535 cp = c & 0x0F;
4536 } else if ((c & 0xF8) == 0xF0) {
4537 len = 4;
4538 cp = c & 0x07;
4539 } else {
4540 return false;
4541 }
4542 if (i + len > n) { return false; }
4543 for (size_t j = 1; j < len; j++) {
4544 auto b = static_cast<unsigned char>(s[i + j]);
4545 if ((b & 0xC0) != 0x80) { return false; }
4546 cp = (cp << 6) | (b & 0x3F);
4547 }
4548 // Overlong encoding check
4549 if (len == 2 && cp < 0x80) { return false; }
4550 if (len == 3 && cp < 0x800) { return false; }
4551 if (len == 4 && cp < 0x10000) { return false; }
4552 // Surrogate halves (U+D800..U+DFFF) and beyond U+10FFFF are invalid
4553 if (cp >= 0xD800 && cp <= 0xDFFF) { return false; }
4554 if (cp > 0x10FFFF) { return false; }
4555 i += len;
4556 }
4557 return true;
4558}
4559
4560} // namespace impl
4561} // namespace ws
4562
4563namespace detail {
4564
4565// NOTE: This code came up with the following stackoverflow post:
4566// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
4567inline std::string base64_encode(const std::string &in) {
4568 static const auto lookup =
4569 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4570
4571 std::string out;
4572 out.reserve(in.size());
4573
4574 auto val = 0;
4575 auto valb = -6;
4576
4577 for (auto c : in) {
4578 val = (val << 8) + static_cast<uint8_t>(c);
4579 valb += 8;
4580 while (valb >= 0) {
4581 out.push_back(lookup[(val >> valb) & 0x3F]);
4582 valb -= 6;
4583 }
4584 }
4585
4586 if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); }
4587
4588 while (out.size() % 4) {
4589 out.push_back('=');
4590 }
4591
4592 return out;
4593}
4594
4595inline std::string sha1(const std::string &input) {
4596 // RFC 3174 SHA-1 implementation
4597 auto left_rotate = [](uint32_t x, uint32_t n) -> uint32_t {
4598 return (x << n) | (x >> (32 - n));
4599 };
4600
4601 uint32_t h0 = 0x67452301;
4602 uint32_t h1 = 0xEFCDAB89;
4603 uint32_t h2 = 0x98BADCFE;
4604 uint32_t h3 = 0x10325476;
4605 uint32_t h4 = 0xC3D2E1F0;
4606
4607 // Pre-processing: adding padding bits
4608 std::string msg = input;
4609 uint64_t original_bit_len = static_cast<uint64_t>(msg.size()) * 8;
4610 msg.push_back(static_cast<char>(0x80));
4611 while (msg.size() % 64 != 56) {
4612 msg.push_back(0);
4613 }
4614
4615 // Append original length in bits as 64-bit big-endian
4616 for (int i = 56; i >= 0; i -= 8) {
4617 msg.push_back(static_cast<char>((original_bit_len >> i) & 0xFF));
4618 }
4619
4620 // Process each 512-bit chunk
4621 for (size_t offset = 0; offset < msg.size(); offset += 64) {
4622 uint32_t w[80];
4623
4624 for (size_t i = 0; i < 16; i++) {
4625 w[i] =
4626 (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4]))
4627 << 24) |
4628 (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4 + 1]))
4629 << 16) |
4630 (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4 + 2]))
4631 << 8) |
4632 (static_cast<uint32_t>(
4633 static_cast<uint8_t>(msg[offset + i * 4 + 3])));
4634 }
4635
4636 for (int i = 16; i < 80; i++) {
4637 w[i] = left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
4638 }
4639
4640 uint32_t a = h0, b = h1, c = h2, d = h3, e = h4;
4641
4642 for (int i = 0; i < 80; i++) {
4643 uint32_t f, k;
4644 if (i < 20) {
4645 f = (b & c) | ((~b) & d);
4646 k = 0x5A827999;
4647 } else if (i < 40) {
4648 f = b ^ c ^ d;
4649 k = 0x6ED9EBA1;
4650 } else if (i < 60) {
4651 f = (b & c) | (b & d) | (c & d);
4652 k = 0x8F1BBCDC;
4653 } else {
4654 f = b ^ c ^ d;
4655 k = 0xCA62C1D6;
4656 }
4657
4658 uint32_t temp = left_rotate(a, 5) + f + e + k + w[i];
4659 e = d;
4660 d = c;
4661 c = left_rotate(b, 30);
4662 b = a;
4663 a = temp;
4664 }
4665
4666 h0 += a;
4667 h1 += b;
4668 h2 += c;
4669 h3 += d;
4670 h4 += e;
4671 }
4672
4673 // Produce the final hash as a 20-byte binary string
4674 std::string hash(20, '\0');
4675 for (size_t i = 0; i < 4; i++) {
4676 hash[i] = static_cast<char>((h0 >> (24 - i * 8)) & 0xFF);
4677 hash[4 + i] = static_cast<char>((h1 >> (24 - i * 8)) & 0xFF);
4678 hash[8 + i] = static_cast<char>((h2 >> (24 - i * 8)) & 0xFF);
4679 hash[12 + i] = static_cast<char>((h3 >> (24 - i * 8)) & 0xFF);
4680 hash[16 + i] = static_cast<char>((h4 >> (24 - i * 8)) & 0xFF);
4681 }
4682 return hash;
4683}
4684
4685inline std::string websocket_accept_key(const std::string &client_key) {
4686 const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
4687 return base64_encode(sha1(client_key + magic));
4688}
4689
4690inline bool is_websocket_upgrade(const Request &req) {
4691 if (req.method != "GET") { return false; }
4692
4693 // Check Upgrade: websocket (case-insensitive)
4694 auto upgrade_it = req.headers.find("Upgrade");
4695 if (upgrade_it == req.headers.end()) { return false; }
4696 auto upgrade_val = case_ignore::to_lower(upgrade_it->second);
4697 if (upgrade_val != "websocket") { return false; }
4698
4699 // Check Connection header contains "Upgrade"
4700 auto connection_it = req.headers.find("Connection");
4701 if (connection_it == req.headers.end()) { return false; }
4702 auto connection_val = case_ignore::to_lower(connection_it->second);
4703 if (connection_val.find("upgrade") == std::string::npos) { return false; }
4704
4705 // Check Sec-WebSocket-Key is a valid base64-encoded 16-byte value (24 chars)
4706 // RFC 6455 Section 4.2.1
4707 auto ws_key = req.get_header_value("Sec-WebSocket-Key");
4708 if (ws_key.size() != 24 || ws_key[22] != '=' || ws_key[23] != '=') {
4709 return false;
4710 }
4711 static const std::string b64chars =
4712 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4713 for (size_t i = 0; i < 22; i++) {
4714 if (b64chars.find(ws_key[i]) == std::string::npos) { return false; }
4715 }
4716
4717 // Check Sec-WebSocket-Version: 13
4718 auto version = req.get_header_value("Sec-WebSocket-Version");
4719 if (version != "13") { return false; }
4720
4721 return true;
4722}
4723
4724inline bool write_websocket_frame(Stream &strm, ws::Opcode opcode,
4725 const char *data, size_t len, bool fin,
4726 bool mask) {
4727 // First byte: FIN + opcode
4728 uint8_t header[2];
4729 header[0] = static_cast<uint8_t>((fin ? 0x80 : 0x00) |
4730 (static_cast<uint8_t>(opcode) & 0x0F));
4731
4732 // Second byte: MASK + payload length
4733 if (len < 126) {
4734 header[1] = static_cast<uint8_t>(len);
4735 if (mask) { header[1] |= 0x80; }
4736 if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4737 } else if (len <= 0xFFFF) {
4738 header[1] = 126;
4739 if (mask) { header[1] |= 0x80; }
4740 if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4741 uint8_t ext[2];
4742 ext[0] = static_cast<uint8_t>((len >> 8) & 0xFF);
4743 ext[1] = static_cast<uint8_t>(len & 0xFF);
4744 if (strm.write(reinterpret_cast<char *>(ext), 2) < 0) { return false; }
4745 } else {
4746 header[1] = 127;
4747 if (mask) { header[1] |= 0x80; }
4748 if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4749 uint8_t ext[8];
4750 for (int i = 7; i >= 0; i--) {
4751 ext[7 - i] = static_cast<uint8_t>((len >> (i * 8)) & 0xFF);
4752 }
4753 if (strm.write(reinterpret_cast<char *>(ext), 8) < 0) { return false; }
4754 }
4755
4756 if (mask) {
4757 // Generate random mask key
4758 thread_local std::mt19937 rng(std::random_device{}());
4759 uint8_t mask_key[4];
4760 auto r = rng();
4761 std::memcpy(mask_key, &r, 4);
4762 if (strm.write(reinterpret_cast<char *>(mask_key), 4) < 0) { return false; }
4763
4764 // Write masked payload in chunks
4765 const size_t chunk_size = 4096;
4766 std::vector<char> buf((std::min)(len, chunk_size));
4767 for (size_t offset = 0; offset < len; offset += chunk_size) {
4768 size_t n = (std::min)(chunk_size, len - offset);
4769 for (size_t i = 0; i < n; i++) {
4770 buf[i] =
4771 data[offset + i] ^ static_cast<char>(mask_key[(offset + i) % 4]);
4772 }
4773 if (strm.write(buf.data(), n) < 0) { return false; }
4774 }
4775 } else {
4776 if (len > 0) {
4777 if (strm.write(data, len) < 0) { return false; }
4778 }
4779 }
4780
4781 return true;
4782}
4783
4784} // namespace detail
4785
4786namespace ws {
4787namespace impl {
4788
4789inline bool read_websocket_frame(Stream &strm, Opcode &opcode,
4790 std::string &payload, bool &fin,
4791 bool expect_masked, size_t max_len) {
4792 // Read first 2 bytes
4793 uint8_t header[2];
4794 if (strm.read(reinterpret_cast<char *>(header), 2) != 2) { return false; }
4795
4796 fin = (header[0] & 0x80) != 0;
4797
4798 // RSV1, RSV2, RSV3 must be 0 when no extension is negotiated
4799 if (header[0] & 0x70) { return false; }
4800
4801 opcode = static_cast<Opcode>(header[0] & 0x0F);
4802 bool masked = (header[1] & 0x80) != 0;
4803 uint64_t payload_len = header[1] & 0x7F;
4804
4805 // RFC 6455 Section 5.5: control frames MUST NOT be fragmented and
4806 // MUST have a payload length of 125 bytes or less
4807 bool is_control = (static_cast<uint8_t>(opcode) & 0x08) != 0;
4808 if (is_control) {
4809 if (!fin) { return false; }
4810 if (payload_len > 125) { return false; }
4811 }
4812
4813 if (masked != expect_masked) { return false; }
4814
4815 // Extended payload length
4816 if (payload_len == 126) {
4817 uint8_t ext[2];
4818 if (strm.read(reinterpret_cast<char *>(ext), 2) != 2) { return false; }
4819 payload_len = (static_cast<uint64_t>(ext[0]) << 8) | ext[1];
4820 } else if (payload_len == 127) {
4821 uint8_t ext[8];
4822 if (strm.read(reinterpret_cast<char *>(ext), 8) != 8) { return false; }
4823 // RFC 6455 Section 5.2: the most significant bit MUST be 0
4824 if (ext[0] & 0x80) { return false; }
4825 payload_len = 0;
4826 for (int i = 0; i < 8; i++) {
4827 payload_len = (payload_len << 8) | ext[i];
4828 }
4829 }
4830
4831 if (payload_len > max_len) { return false; }
4832
4833 // Read mask key if present
4834 uint8_t mask_key[4] = {0};
4835 if (masked) {
4836 if (strm.read(reinterpret_cast<char *>(mask_key), 4) != 4) { return false; }
4837 }
4838
4839 // Read payload
4840 payload.resize(static_cast<size_t>(payload_len));
4841 if (payload_len > 0) {
4842 size_t total_read = 0;
4843 while (total_read < payload_len) {
4844 auto n = strm.read(&payload[total_read],
4845 static_cast<size_t>(payload_len - total_read));
4846 if (n <= 0) { return false; }
4847 total_read += static_cast<size_t>(n);
4848 }
4849 }
4850
4851 // Unmask if needed
4852 if (masked) {
4853 for (size_t i = 0; i < payload.size(); i++) {
4854 payload[i] ^= static_cast<char>(mask_key[i % 4]);
4855 }
4856 }
4857
4858 return true;
4859}
4860
4861} // namespace impl
4862} // namespace ws
4863
4864namespace detail {
4865
4866inline bool is_valid_path(const std::string &path) {
4867 size_t level = 0;
4868 size_t i = 0;
4869
4870 // Skip slash
4871 while (i < path.size() && path[i] == '/') {
4872 i++;
4873 }
4874
4875 while (i < path.size()) {
4876 // Read component
4877 auto beg = i;
4878 while (i < path.size() && path[i] != '/') {
4879 if (path[i] == '\0') {
4880 return false;
4881 } else if (path[i] == '\\') {
4882 return false;
4883 }
4884 i++;
4885 }
4886
4887 auto len = i - beg;
4888 assert(len > 0);
4889
4890 if (!path.compare(beg, len, ".")) {
4891 ;
4892 } else if (!path.compare(beg, len, "..")) {
4893 if (level == 0) { return false; }
4894 level--;
4895 } else {
4896 level++;
4897 }
4898
4899 // Skip slash
4900 while (i < path.size() && path[i] == '/') {
4901 i++;
4902 }
4903 }
4904
4905 return true;
4906}
4907
4908inline bool canonicalize_path(const char *path, std::string &resolved) {
4909#if defined(_WIN32)
4910 char buf[_MAX_PATH];
4911 if (_fullpath(buf, path, _MAX_PATH) == nullptr) { return false; }
4912 resolved = buf;
4913#else
4914 char buf[PATH_MAX];
4915 if (realpath(path, buf) == nullptr) { return false; }
4916 resolved = buf;
4917#endif
4918 return true;
4919}
4920
4921inline bool is_path_within_base(const std::string &resolved_path,
4922 const std::string &resolved_base) {
4923#if defined(_WIN32)
4924 return _strnicmp(resolved_path.c_str(), resolved_base.c_str(),
4925 resolved_base.size()) == 0;
4926#else
4927 return strncmp(resolved_path.c_str(), resolved_base.c_str(),
4928 resolved_base.size()) == 0;
4929#endif
4930}
4931
4932inline FileStat::FileStat(const std::string &path) {
4933#if defined(_WIN32)
4934 auto wpath = u8string_to_wstring(path.c_str());
4935 ret_ = _wstat(wpath.c_str(), &st_);
4936#else
4937 ret_ = stat(path.c_str(), &st_);
4938#endif
4939}
4940inline bool FileStat::is_file() const {
4941 return ret_ >= 0 && S_ISREG(st_.st_mode);
4942}
4943inline bool FileStat::is_dir() const {
4944 return ret_ >= 0 && S_ISDIR(st_.st_mode);
4945}
4946
4947inline time_t FileStat::mtime() const {
4948 return ret_ >= 0 ? static_cast<time_t>(st_.st_mtime)
4949 : static_cast<time_t>(-1);
4950}
4951
4952inline size_t FileStat::size() const {
4953 return ret_ >= 0 ? static_cast<size_t>(st_.st_size) : 0;
4954}
4955
4956inline std::string encode_path(const std::string &s) {
4957 std::string result;
4958 result.reserve(s.size());
4959
4960 for (size_t i = 0; s[i]; i++) {
4961 switch (s[i]) {
4962 case ' ': result += "%20"; break;
4963 case '+': result += "%2B"; break;
4964 case '\r': result += "%0D"; break;
4965 case '\n': result += "%0A"; break;
4966 case '\'': result += "%27"; break;
4967 case ',': result += "%2C"; break;
4968 // case ':': result += "%3A"; break; // ok? probably...
4969 case ';': result += "%3B"; break;
4970 default:
4971 auto c = static_cast<uint8_t>(s[i]);
4972 if (c >= 0x80) {
4973 result += '%';
4974 char hex[4];
4975 auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
4976 assert(len == 2);
4977 result.append(hex, static_cast<size_t>(len));
4978 } else {
4979 result += s[i];
4980 }
4981 break;
4982 }
4983 }
4984
4985 return result;
4986}
4987
4988inline std::string file_extension(const std::string &path) {
4989 std::smatch m;
4990 thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$");
4991 if (std::regex_search(path, m, re)) { return m[1].str(); }
4992 return std::string();
4993}
4994
4995inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
4996
4997template <typename T>
4998inline bool parse_header(const char *beg, const char *end, T fn);
4999
5000template <typename T>
5001inline bool parse_header(const char *beg, const char *end, T fn) {
5002 // Skip trailing spaces and tabs.
5003 while (beg < end && is_space_or_tab(end[-1])) {
5004 end--;
5005 }
5006
5007 auto p = beg;
5008 while (p < end && *p != ':') {
5009 p++;
5010 }
5011
5012 auto name = std::string(beg, p);
5013 if (!detail::fields::is_field_name(name)) { return false; }
5014
5015 if (p == end) { return false; }
5016
5017 auto key_end = p;
5018
5019 if (*p++ != ':') { return false; }
5020
5021 while (p < end && is_space_or_tab(*p)) {
5022 p++;
5023 }
5024
5025 if (p <= end) {
5026 auto key_len = key_end - beg;
5027 if (!key_len) { return false; }
5028
5029 auto key = std::string(beg, key_end);
5030 auto val = std::string(p, end);
5031
5032 if (!detail::fields::is_field_value(val)) { return false; }
5033
5034 if (case_ignore::equal(key, "Location") ||
5035 case_ignore::equal(key, "Referer")) {
5036 fn(key, val);
5037 } else {
5038 fn(key, decode_path_component(val));
5039 }
5040
5041 return true;
5042 }
5043
5044 return false;
5045}
5046
5047inline bool parse_trailers(stream_line_reader &line_reader, Headers &dest,
5048 const Headers &src_headers) {
5049 // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked
5050 // transfer coding is complete when a chunk with a chunk-size of zero is
5051 // received, possibly followed by a trailer section, and finally terminated by
5052 // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1
5053 //
5054 // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section
5055 // doesn't care for the existence of the final CRLF. In other words, it seems
5056 // to be ok whether the final CRLF exists or not in the chunked data.
5057 // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3
5058 //
5059 // According to the reference code in RFC 9112, cpp-httplib now allows
5060 // chunked transfer coding data without the final CRLF.
5061
5062 // RFC 7230 Section 4.1.2 - Headers prohibited in trailers
5063 thread_local case_ignore::unordered_set<std::string> prohibited_trailers = {
5064 "transfer-encoding",
5065 "content-length",
5066 "host",
5067 "authorization",
5068 "www-authenticate",
5069 "proxy-authenticate",
5070 "proxy-authorization",
5071 "cookie",
5072 "set-cookie",
5073 "cache-control",
5074 "expect",
5075 "max-forwards",
5076 "pragma",
5077 "range",
5078 "te",
5079 "age",
5080 "expires",
5081 "date",
5082 "location",
5083 "retry-after",
5084 "vary",
5085 "warning",
5086 "content-encoding",
5087 "content-type",
5088 "content-range",
5089 "trailer"};
5090
5092 auto trailer_header = get_header_value(src_headers, "Trailer", "", 0);
5093 if (trailer_header && std::strlen(trailer_header)) {
5094 auto len = std::strlen(trailer_header);
5095 split(trailer_header, trailer_header + len, ',',
5096 [&](const char *b, const char *e) {
5097 const char *kbeg = b;
5098 const char *kend = e;
5099 while (kbeg < kend && (*kbeg == ' ' || *kbeg == '\t')) {
5100 ++kbeg;
5101 }
5102 while (kend > kbeg && (kend[-1] == ' ' || kend[-1] == '\t')) {
5103 --kend;
5104 }
5105 std::string key(kbeg, static_cast<size_t>(kend - kbeg));
5106 if (!key.empty() &&
5107 prohibited_trailers.find(key) == prohibited_trailers.end()) {
5108 declared_trailers.insert(key);
5109 }
5110 });
5111 }
5112
5113 size_t trailer_header_count = 0;
5114 while (strcmp(line_reader.ptr(), "\r\n") != 0) {
5115 if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
5116 if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
5117
5118 constexpr auto line_terminator_len = 2;
5119 auto line_beg = line_reader.ptr();
5120 auto line_end =
5121 line_reader.ptr() + line_reader.size() - line_terminator_len;
5122
5123 if (!parse_header(line_beg, line_end,
5124 [&](const std::string &key, const std::string &val) {
5125 if (declared_trailers.find(key) !=
5126 declared_trailers.end()) {
5127 dest.emplace(key, val);
5128 trailer_header_count++;
5129 }
5130 })) {
5131 return false;
5132 }
5133
5134 if (!line_reader.getline()) { return false; }
5135 }
5136
5137 return true;
5138}
5139
5140inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
5141 size_t right) {
5142 while (b + left < e && is_space_or_tab(b[left])) {
5143 left++;
5144 }
5145 while (right > 0 && is_space_or_tab(b[right - 1])) {
5146 right--;
5147 }
5148 return std::make_pair(left, right);
5149}
5150
5151inline std::string trim_copy(const std::string &s) {
5152 auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
5153 return s.substr(r.first, r.second - r.first);
5154}
5155
5156inline std::string trim_double_quotes_copy(const std::string &s) {
5157 if (s.length() >= 2 && s.front() == '"' && s.back() == '"') {
5158 return s.substr(1, s.size() - 2);
5159 }
5160 return s;
5161}
5162
5163inline void
5164divide(const char *data, std::size_t size, char d,
5165 std::function<void(const char *, std::size_t, const char *, std::size_t)>
5166 fn) {
5167 const auto it = std::find(data, data + size, d);
5168 const auto found = static_cast<std::size_t>(it != data + size);
5169 const auto lhs_data = data;
5170 const auto lhs_size = static_cast<std::size_t>(it - data);
5171 const auto rhs_data = it + found;
5172 const auto rhs_size = size - lhs_size - found;
5173
5174 fn(lhs_data, lhs_size, rhs_data, rhs_size);
5175}
5176
5177inline void
5178divide(const std::string &str, char d,
5179 std::function<void(const char *, std::size_t, const char *, std::size_t)>
5180 fn) {
5181 divide(str.data(), str.size(), d, std::move(fn));
5182}
5183
5184inline void split(const char *b, const char *e, char d,
5185 std::function<void(const char *, const char *)> fn) {
5186 return split(b, e, d, (std::numeric_limits<size_t>::max)(), std::move(fn));
5187}
5188
5189inline void split(const char *b, const char *e, char d, size_t m,
5190 std::function<void(const char *, const char *)> fn) {
5191 size_t i = 0;
5192 size_t beg = 0;
5193 size_t count = 1;
5194
5195 while (e ? (b + i < e) : (b[i] != '\0')) {
5196 if (b[i] == d && count < m) {
5197 auto r = trim(b, e, beg, i);
5198 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
5199 beg = i + 1;
5200 count++;
5201 }
5202 i++;
5203 }
5204
5205 if (i) {
5206 auto r = trim(b, e, beg, i);
5207 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
5208 }
5209}
5210
5211inline bool split_find(const char *b, const char *e, char d, size_t m,
5212 std::function<bool(const char *, const char *)> fn) {
5213 size_t i = 0;
5214 size_t beg = 0;
5215 size_t count = 1;
5216
5217 while (e ? (b + i < e) : (b[i] != '\0')) {
5218 if (b[i] == d && count < m) {
5219 auto r = trim(b, e, beg, i);
5220 if (r.first < r.second) {
5221 auto found = fn(&b[r.first], &b[r.second]);
5222 if (found) { return true; }
5223 }
5224 beg = i + 1;
5225 count++;
5226 }
5227 i++;
5228 }
5229
5230 if (i) {
5231 auto r = trim(b, e, beg, i);
5232 if (r.first < r.second) {
5233 auto found = fn(&b[r.first], &b[r.second]);
5234 if (found) { return true; }
5235 }
5236 }
5237
5238 return false;
5239}
5240
5241inline bool split_find(const char *b, const char *e, char d,
5242 std::function<bool(const char *, const char *)> fn) {
5243 return split_find(b, e, d, (std::numeric_limits<size_t>::max)(),
5244 std::move(fn));
5245}
5246
5247inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer,
5248 size_t fixed_buffer_size)
5249 : strm_(strm), fixed_buffer_(fixed_buffer),
5250 fixed_buffer_size_(fixed_buffer_size) {}
5251
5252inline const char *stream_line_reader::ptr() const {
5253 if (growable_buffer_.empty()) {
5254 return fixed_buffer_;
5255 } else {
5256 return growable_buffer_.data();
5257 }
5258}
5259
5260inline size_t stream_line_reader::size() const {
5261 if (growable_buffer_.empty()) {
5262 return fixed_buffer_used_size_;
5263 } else {
5264 return growable_buffer_.size();
5265 }
5266}
5267
5269 auto end = ptr() + size();
5270 return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
5271}
5272
5274 fixed_buffer_used_size_ = 0;
5275 growable_buffer_.clear();
5276
5277#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
5278 char prev_byte = 0;
5279#endif
5280
5281 for (size_t i = 0;; i++) {
5283 // Treat exceptionally long lines as an error to
5284 // prevent infinite loops/memory exhaustion
5285 return false;
5286 }
5287 char byte;
5288 auto n = strm_.read(&byte, 1);
5289
5290 if (n < 0) {
5291 return false;
5292 } else if (n == 0) {
5293 if (i == 0) {
5294 return false;
5295 } else {
5296 break;
5297 }
5298 }
5299
5300 append(byte);
5301
5302#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
5303 if (byte == '\n') { break; }
5304#else
5305 if (prev_byte == '\r' && byte == '\n') { break; }
5306 prev_byte = byte;
5307#endif
5308 }
5309
5310 return true;
5311}
5312
5313inline void stream_line_reader::append(char c) {
5314 if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) {
5315 fixed_buffer_[fixed_buffer_used_size_++] = c;
5316 fixed_buffer_[fixed_buffer_used_size_] = '\0';
5317 } else {
5318 if (growable_buffer_.empty()) {
5319 assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
5320 growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
5321 }
5322 growable_buffer_ += c;
5323 }
5324}
5325
5326inline mmap::mmap(const char *path) { open(path); }
5327
5328inline mmap::~mmap() { close(); }
5329
5330inline bool mmap::open(const char *path) {
5331 close();
5332
5333#if defined(_WIN32)
5334 auto wpath = u8string_to_wstring(path);
5335 if (wpath.empty()) { return false; }
5336
5337 hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
5338 OPEN_EXISTING, NULL);
5339
5340 if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
5341
5342 LARGE_INTEGER size{};
5343 if (!::GetFileSizeEx(hFile_, &size)) { return false; }
5344 // If the following line doesn't compile due to QuadPart, update Windows SDK.
5345 // See:
5346 // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721
5347 if (static_cast<ULONGLONG>(size.QuadPart) >
5348 (std::numeric_limits<decltype(size_)>::max)()) {
5349 // `size_t` might be 32-bits, on 32-bits Windows.
5350 return false;
5351 }
5352 size_ = static_cast<size_t>(size.QuadPart);
5353
5354 hMapping_ =
5355 ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
5356
5357 // Special treatment for an empty file...
5358 if (hMapping_ == NULL && size_ == 0) {
5359 close();
5360 is_open_empty_file = true;
5361 return true;
5362 }
5363
5364 if (hMapping_ == NULL) {
5365 close();
5366 return false;
5367 }
5368
5369 addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
5370
5371 if (addr_ == nullptr) {
5372 close();
5373 return false;
5374 }
5375#else
5376 fd_ = ::open(path, O_RDONLY);
5377 if (fd_ == -1) { return false; }
5378
5379 struct stat sb;
5380 if (fstat(fd_, &sb) == -1) {
5381 close();
5382 return false;
5383 }
5384 size_ = static_cast<size_t>(sb.st_size);
5385
5386 addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
5387
5388 // Special treatment for an empty file...
5389 if (addr_ == MAP_FAILED && size_ == 0) {
5390 close();
5391 is_open_empty_file = true;
5392 return false;
5393 }
5394#endif
5395
5396 return true;
5397}
5398
5399inline bool mmap::is_open() const {
5400 return is_open_empty_file ? true : addr_ != nullptr;
5401}
5402
5403inline size_t mmap::size() const { return size_; }
5404
5405inline const char *mmap::data() const {
5406 return is_open_empty_file ? "" : static_cast<const char *>(addr_);
5407}
5408
5409inline void mmap::close() {
5410#if defined(_WIN32)
5411 if (addr_) {
5412 ::UnmapViewOfFile(addr_);
5413 addr_ = nullptr;
5414 }
5415
5416 if (hMapping_) {
5417 ::CloseHandle(hMapping_);
5418 hMapping_ = NULL;
5419 }
5420
5421 if (hFile_ != INVALID_HANDLE_VALUE) {
5422 ::CloseHandle(hFile_);
5423 hFile_ = INVALID_HANDLE_VALUE;
5424 }
5425
5426 is_open_empty_file = false;
5427#else
5428 if (addr_ != nullptr) {
5429 munmap(addr_, size_);
5430 addr_ = nullptr;
5431 }
5432
5433 if (fd_ != -1) {
5434 ::close(fd_);
5435 fd_ = -1;
5436 }
5437#endif
5438 size_ = 0;
5439}
5440inline int close_socket(socket_t sock) {
5441#ifdef _WIN32
5442 return closesocket(sock);
5443#else
5444 return close(sock);
5445#endif
5446}
5447
5448template <typename T> inline ssize_t handle_EINTR(T fn) {
5449 ssize_t res = 0;
5450 while (true) {
5451 res = fn();
5452 if (res < 0 && errno == EINTR) {
5453 std::this_thread::sleep_for(std::chrono::microseconds{1});
5454 continue;
5455 }
5456 break;
5457 }
5458 return res;
5459}
5460
5461inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) {
5462 return handle_EINTR([&]() {
5463 return recv(sock,
5464#ifdef _WIN32
5465 static_cast<char *>(ptr), static_cast<int>(size),
5466#else
5467 ptr, size,
5468#endif
5469 flags);
5470 });
5471}
5472
5473inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
5474 int flags) {
5475 return handle_EINTR([&]() {
5476 return send(sock,
5477#ifdef _WIN32
5478 static_cast<const char *>(ptr), static_cast<int>(size),
5479#else
5480 ptr, size,
5481#endif
5482 flags);
5483 });
5484}
5485
5486inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) {
5487#ifdef _WIN32
5488 return ::WSAPoll(fds, nfds, timeout);
5489#else
5490 return ::poll(fds, nfds, timeout);
5491#endif
5492}
5493
5494inline ssize_t select_impl(socket_t sock, short events, time_t sec,
5495 time_t usec) {
5496 struct pollfd pfd;
5497 pfd.fd = sock;
5498 pfd.events = events;
5499 pfd.revents = 0;
5500
5501 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
5502
5503 return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); });
5504}
5505
5506inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
5507 return select_impl(sock, POLLIN, sec, usec);
5508}
5509
5510inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
5511 return select_impl(sock, POLLOUT, sec, usec);
5512}
5513
5515 time_t usec) {
5516 struct pollfd pfd_read;
5517 pfd_read.fd = sock;
5518 pfd_read.events = POLLIN | POLLOUT;
5519 pfd_read.revents = 0;
5520
5521 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
5522
5523 auto poll_res =
5524 handle_EINTR([&]() { return poll_wrapper(&pfd_read, 1, timeout); });
5525
5526 if (poll_res == 0) { return Error::ConnectionTimeout; }
5527
5528 if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
5529 auto error = 0;
5530 socklen_t len = sizeof(error);
5531 auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
5532 reinterpret_cast<char *>(&error), &len);
5533 auto successful = res >= 0 && !error;
5534 return successful ? Error::Success : Error::Connection;
5535 }
5536
5537 return Error::Connection;
5538}
5539
5540inline bool is_socket_alive(socket_t sock) {
5541 const auto val = detail::select_read(sock, 0, 0);
5542 if (val == 0) {
5543 return true;
5544 } else if (val < 0 && errno == EBADF) {
5545 return false;
5546 }
5547 char buf[1];
5548 return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0;
5549}
5550
5551class SocketStream final : public Stream {
5552public:
5553 SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
5554 time_t write_timeout_sec, time_t write_timeout_usec,
5555 time_t max_timeout_msec = 0,
5556 std::chrono::time_point<std::chrono::steady_clock> start_time =
5557 (std::chrono::steady_clock::time_point::min)());
5558 ~SocketStream() override;
5559
5560 bool is_readable() const override;
5561 bool wait_readable() const override;
5562 bool wait_writable() const override;
5563 bool is_peer_alive() const override;
5564 ssize_t read(char *ptr, size_t size) override;
5565 ssize_t write(const char *ptr, size_t size) override;
5566 void get_remote_ip_and_port(std::string &ip, int &port) const override;
5567 void get_local_ip_and_port(std::string &ip, int &port) const override;
5568 socket_t socket() const override;
5569 time_t duration() const override;
5570 void set_read_timeout(time_t sec, time_t usec = 0) override;
5571
5572private:
5573 socket_t sock_;
5574 time_t read_timeout_sec_;
5575 time_t read_timeout_usec_;
5576 time_t write_timeout_sec_;
5577 time_t write_timeout_usec_;
5578 time_t max_timeout_msec_;
5579 const std::chrono::time_point<std::chrono::steady_clock> start_time_;
5580
5581 std::vector<char> read_buff_;
5582 size_t read_buff_off_ = 0;
5583 size_t read_buff_content_size_ = 0;
5584
5585 static const size_t read_buff_size_ = 1024l * 4;
5586};
5587
5588inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
5589 time_t keep_alive_timeout_sec) {
5590 using namespace std::chrono;
5591
5592 const auto interval_usec =
5594
5595 // Avoid expensive `steady_clock::now()` call for the first time
5596 if (select_read(sock, 0, interval_usec) > 0) { return true; }
5597
5598 const auto start = steady_clock::now() - microseconds{interval_usec};
5599 const auto timeout = seconds{keep_alive_timeout_sec};
5600
5601 while (true) {
5602 if (svr_sock == INVALID_SOCKET) {
5603 break; // Server socket is closed
5604 }
5605
5606 auto val = select_read(sock, 0, interval_usec);
5607 if (val < 0) {
5608 break; // Ssocket error
5609 } else if (val == 0) {
5610 if (steady_clock::now() - start > timeout) {
5611 break; // Timeout
5612 }
5613 } else {
5614 return true; // Ready for read
5615 }
5616 }
5617
5618 return false;
5619}
5620
5621template <typename T>
5622inline bool
5623process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
5624 size_t keep_alive_max_count,
5625 time_t keep_alive_timeout_sec, T callback) {
5626 assert(keep_alive_max_count > 0);
5627 auto ret = false;
5628 auto count = keep_alive_max_count;
5629 while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
5630 auto close_connection = count == 1;
5631 auto connection_closed = false;
5632 ret = callback(close_connection, connection_closed);
5633 if (!ret || connection_closed) { break; }
5634 count--;
5635 }
5636 return ret;
5637}
5638
5639template <typename T>
5640inline bool
5641process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
5642 size_t keep_alive_max_count,
5643 time_t keep_alive_timeout_sec, time_t read_timeout_sec,
5644 time_t read_timeout_usec, time_t write_timeout_sec,
5645 time_t write_timeout_usec, T callback) {
5647 svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
5648 [&](bool close_connection, bool &connection_closed) {
5649 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
5650 write_timeout_sec, write_timeout_usec);
5651 return callback(strm, close_connection, connection_closed);
5652 });
5653}
5654
5656 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
5657 time_t write_timeout_sec, time_t write_timeout_usec,
5658 time_t max_timeout_msec,
5659 std::chrono::time_point<std::chrono::steady_clock> start_time,
5660 std::function<bool(Stream &)> callback) {
5661 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
5662 write_timeout_sec, write_timeout_usec, max_timeout_msec,
5663 start_time);
5664 return callback(strm);
5665}
5666
5667inline int shutdown_socket(socket_t sock) {
5668#ifdef _WIN32
5669 return shutdown(sock, SD_BOTH);
5670#else
5671 return shutdown(sock, SHUT_RDWR);
5672#endif
5673}
5674
5675inline std::string escape_abstract_namespace_unix_domain(const std::string &s) {
5676 if (s.size() > 1 && s[0] == '\0') {
5677 auto ret = s;
5678 ret[0] = '@';
5679 return ret;
5680 }
5681 return s;
5682}
5683
5684inline std::string
5686 if (s.size() > 1 && s[0] == '@') {
5687 auto ret = s;
5688 ret[0] = '\0';
5689 return ret;
5690 }
5691 return s;
5692}
5693
5694inline int getaddrinfo_with_timeout(const char *node, const char *service,
5695 const struct addrinfo *hints,
5696 struct addrinfo **res, time_t timeout_sec) {
5697#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO
5698 if (timeout_sec <= 0) {
5699 // No timeout specified, use standard getaddrinfo
5700 return getaddrinfo(node, service, hints, res);
5701 }
5702
5703#ifdef _WIN32
5704 // Windows-specific implementation using GetAddrInfoEx with overlapped I/O
5705 OVERLAPPED overlapped = {0};
5706 HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
5707 if (!event) { return EAI_FAIL; }
5708
5709 overlapped.hEvent = event;
5710
5711 PADDRINFOEXW result_addrinfo = nullptr;
5712 HANDLE cancel_handle = nullptr;
5713
5714 ADDRINFOEXW hints_ex = {0};
5715 if (hints) {
5716 hints_ex.ai_flags = hints->ai_flags;
5717 hints_ex.ai_family = hints->ai_family;
5718 hints_ex.ai_socktype = hints->ai_socktype;
5719 hints_ex.ai_protocol = hints->ai_protocol;
5720 }
5721
5722 auto wnode = u8string_to_wstring(node);
5723 auto wservice = u8string_to_wstring(service);
5724
5725 auto ret = ::GetAddrInfoExW(wnode.data(), wservice.data(), NS_DNS, nullptr,
5726 hints ? &hints_ex : nullptr, &result_addrinfo,
5727 nullptr, &overlapped, nullptr, &cancel_handle);
5728
5729 if (ret == WSA_IO_PENDING) {
5730 auto wait_result =
5731 ::WaitForSingleObject(event, static_cast<DWORD>(timeout_sec * 1000));
5732 if (wait_result == WAIT_TIMEOUT) {
5733 if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); }
5734 ::CloseHandle(event);
5735 return EAI_AGAIN;
5736 }
5737
5738 DWORD bytes_returned;
5739 if (!::GetOverlappedResult((HANDLE)INVALID_SOCKET, &overlapped,
5740 &bytes_returned, FALSE)) {
5741 ::CloseHandle(event);
5742 return ::WSAGetLastError();
5743 }
5744 }
5745
5746 ::CloseHandle(event);
5747
5748 if (ret == NO_ERROR || ret == WSA_IO_PENDING) {
5749 *res = reinterpret_cast<struct addrinfo *>(result_addrinfo);
5750 return 0;
5751 }
5752
5753 return ret;
5754#elif TARGET_OS_MAC
5755 if (!node) { return EAI_NONAME; }
5756 // macOS implementation using CFHost API for asynchronous DNS resolution
5757 CFStringRef hostname_ref = CFStringCreateWithCString(
5758 kCFAllocatorDefault, node, kCFStringEncodingUTF8);
5759 if (!hostname_ref) { return EAI_MEMORY; }
5760
5761 CFHostRef host_ref = CFHostCreateWithName(kCFAllocatorDefault, hostname_ref);
5762 CFRelease(hostname_ref);
5763 if (!host_ref) { return EAI_MEMORY; }
5764
5765 // Set up context for callback
5766 struct CFHostContext {
5767 bool completed = false;
5768 bool success = false;
5769 CFArrayRef addresses = nullptr;
5770 std::mutex mutex;
5771 std::condition_variable cv;
5772 } context;
5773
5774 CFHostClientContext client_context;
5775 memset(&client_context, 0, sizeof(client_context));
5776 client_context.info = &context;
5777
5778 // Set callback
5779 auto callback = [](CFHostRef theHost, CFHostInfoType /*typeInfo*/,
5780 const CFStreamError *error, void *info) {
5781 auto ctx = static_cast<CFHostContext *>(info);
5782 std::lock_guard<std::mutex> lock(ctx->mutex);
5783
5784 if (error && error->error != 0) {
5785 ctx->success = false;
5786 } else {
5787 Boolean hasBeenResolved;
5788 ctx->addresses = CFHostGetAddressing(theHost, &hasBeenResolved);
5789 if (ctx->addresses && hasBeenResolved) {
5790 CFRetain(ctx->addresses);
5791 ctx->success = true;
5792 } else {
5793 ctx->success = false;
5794 }
5795 }
5796 ctx->completed = true;
5797 ctx->cv.notify_one();
5798 };
5799
5800 if (!CFHostSetClient(host_ref, callback, &client_context)) {
5801 CFRelease(host_ref);
5802 return EAI_SYSTEM;
5803 }
5804
5805 // Schedule on run loop
5806 CFRunLoopRef run_loop = CFRunLoopGetCurrent();
5807 CFHostScheduleWithRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5808
5809 // Start resolution
5810 CFStreamError stream_error;
5811 if (!CFHostStartInfoResolution(host_ref, kCFHostAddresses, &stream_error)) {
5812 CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5813 CFRelease(host_ref);
5814 return EAI_FAIL;
5815 }
5816
5817 // Wait for completion with timeout
5818 auto timeout_time =
5819 std::chrono::steady_clock::now() + std::chrono::seconds(timeout_sec);
5820 bool timed_out = false;
5821
5822 {
5823 std::unique_lock<std::mutex> lock(context.mutex);
5824
5825 while (!context.completed) {
5826 auto now = std::chrono::steady_clock::now();
5827 if (now >= timeout_time) {
5828 timed_out = true;
5829 break;
5830 }
5831
5832 // Run the runloop for a short time
5833 lock.unlock();
5834 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true);
5835 lock.lock();
5836 }
5837 }
5838
5839 // Clean up
5840 CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5841 CFHostSetClient(host_ref, nullptr, nullptr);
5842
5843 if (timed_out || !context.completed) {
5844 CFHostCancelInfoResolution(host_ref, kCFHostAddresses);
5845 CFRelease(host_ref);
5846 return EAI_AGAIN;
5847 }
5848
5849 if (!context.success || !context.addresses) {
5850 CFRelease(host_ref);
5851 return EAI_NODATA;
5852 }
5853
5854 // Convert CFArray to addrinfo
5855 CFIndex count = CFArrayGetCount(context.addresses);
5856 if (count == 0) {
5857 CFRelease(context.addresses);
5858 CFRelease(host_ref);
5859 return EAI_NODATA;
5860 }
5861
5862 struct addrinfo *result_addrinfo = nullptr;
5863 struct addrinfo **current = &result_addrinfo;
5864
5865 for (CFIndex i = 0; i < count; i++) {
5866 CFDataRef addr_data =
5867 static_cast<CFDataRef>(CFArrayGetValueAtIndex(context.addresses, i));
5868 if (!addr_data) continue;
5869
5870 const struct sockaddr *sockaddr_ptr =
5871 reinterpret_cast<const struct sockaddr *>(CFDataGetBytePtr(addr_data));
5872 socklen_t sockaddr_len = static_cast<socklen_t>(CFDataGetLength(addr_data));
5873
5874 // Allocate addrinfo structure
5875 *current = static_cast<struct addrinfo *>(malloc(sizeof(struct addrinfo)));
5876 if (!*current) {
5877 freeaddrinfo(result_addrinfo);
5878 CFRelease(context.addresses);
5879 CFRelease(host_ref);
5880 return EAI_MEMORY;
5881 }
5882
5883 memset(*current, 0, sizeof(struct addrinfo));
5884
5885 // Set up addrinfo fields
5886 (*current)->ai_family = sockaddr_ptr->sa_family;
5887 (*current)->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM;
5888 (*current)->ai_protocol = hints ? hints->ai_protocol : IPPROTO_TCP;
5889 (*current)->ai_addrlen = sockaddr_len;
5890
5891 // Copy sockaddr
5892 (*current)->ai_addr = static_cast<struct sockaddr *>(malloc(sockaddr_len));
5893 if (!(*current)->ai_addr) {
5894 freeaddrinfo(result_addrinfo);
5895 CFRelease(context.addresses);
5896 CFRelease(host_ref);
5897 return EAI_MEMORY;
5898 }
5899 memcpy((*current)->ai_addr, sockaddr_ptr, sockaddr_len);
5900
5901 // Set port if service is specified
5902 if (service && *service) {
5903 int port = 0;
5904 if (parse_port(service, strlen(service), port)) {
5905 if (sockaddr_ptr->sa_family == AF_INET) {
5906 reinterpret_cast<struct sockaddr_in *>((*current)->ai_addr)
5907 ->sin_port = htons(static_cast<uint16_t>(port));
5908 } else if (sockaddr_ptr->sa_family == AF_INET6) {
5909 reinterpret_cast<struct sockaddr_in6 *>((*current)->ai_addr)
5910 ->sin6_port = htons(static_cast<uint16_t>(port));
5911 }
5912 }
5913 }
5914
5915 current = &((*current)->ai_next);
5916 }
5917
5918 CFRelease(context.addresses);
5919 CFRelease(host_ref);
5920
5921 *res = result_addrinfo;
5922 return 0;
5923#elif defined(_GNU_SOURCE) && defined(__GLIBC__) && \
5924 (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2))
5925 // Linux implementation using getaddrinfo_a for asynchronous DNS resolution
5926 struct gaicb request;
5927 struct gaicb *requests[1] = {&request};
5928 struct sigevent sevp;
5929 struct timespec timeout;
5930
5931 // Initialize the request structure
5932 memset(&request, 0, sizeof(request));
5933 request.ar_name = node;
5934 request.ar_service = service;
5935 request.ar_request = hints;
5936
5937 // Set up timeout
5938 timeout.tv_sec = timeout_sec;
5939 timeout.tv_nsec = 0;
5940
5941 // Initialize sigevent structure (not used, but required)
5942 memset(&sevp, 0, sizeof(sevp));
5943 sevp.sigev_notify = SIGEV_NONE;
5944
5945 // Start asynchronous resolution
5946 int start_result = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp);
5947 if (start_result != 0) { return start_result; }
5948
5949 // Wait for completion with timeout
5950 int wait_result =
5951 gai_suspend((const struct gaicb *const *)requests, 1, &timeout);
5952
5953 if (wait_result == 0 || wait_result == EAI_ALLDONE) {
5954 // Completed successfully, get the result
5955 int gai_result = gai_error(&request);
5956 if (gai_result == 0) {
5957 *res = request.ar_result;
5958 return 0;
5959 } else {
5960 // Clean up on error
5961 if (request.ar_result) { freeaddrinfo(request.ar_result); }
5962 return gai_result;
5963 }
5964 } else if (wait_result == EAI_AGAIN) {
5965 // Timeout occurred, cancel the request
5966 gai_cancel(&request);
5967 return EAI_AGAIN;
5968 } else {
5969 // Other error occurred
5970 gai_cancel(&request);
5971 return wait_result;
5972 }
5973#else
5974 // Fallback implementation using thread-based timeout for other Unix systems
5975
5976 struct GetAddrInfoState {
5977 ~GetAddrInfoState() {
5978 if (info) { freeaddrinfo(info); }
5979 }
5980
5981 std::mutex mutex;
5982 std::condition_variable result_cv;
5983 bool completed = false;
5984 int result = EAI_SYSTEM;
5985 std::string node;
5986 std::string service;
5987 struct addrinfo hints;
5988 struct addrinfo *info = nullptr;
5989 };
5990
5991 // Allocate on the heap, so the resolver thread can keep using the data.
5992 auto state = std::make_shared<GetAddrInfoState>();
5993 if (node) { state->node = node; }
5994 state->service = service;
5995 state->hints = *hints;
5996
5997 std::thread resolve_thread([state]() {
5998 auto thread_result =
5999 getaddrinfo(state->node.c_str(), state->service.c_str(), &state->hints,
6000 &state->info);
6001
6002 std::lock_guard<std::mutex> lock(state->mutex);
6003 state->result = thread_result;
6004 state->completed = true;
6005 state->result_cv.notify_one();
6006 });
6007
6008 // Wait for completion or timeout
6009 std::unique_lock<std::mutex> lock(state->mutex);
6010 auto finished =
6011 state->result_cv.wait_for(lock, std::chrono::seconds(timeout_sec),
6012 [&] { return state->completed; });
6013
6014 if (finished) {
6015 // Operation completed within timeout
6016 resolve_thread.join();
6017 *res = state->info;
6018 state->info = nullptr; // Pass ownership to caller
6019 return state->result;
6020 } else {
6021 // Timeout occurred
6022 resolve_thread.detach(); // Let the thread finish in background
6023 return EAI_AGAIN; // Return timeout error
6024 }
6025#endif
6026#else
6027 (void)(timeout_sec); // Unused parameter for non-blocking getaddrinfo
6028 return getaddrinfo(node, service, hints, res);
6029#endif
6030}
6031
6032template <typename BindOrConnect>
6033socket_t create_socket(const std::string &host, const std::string &ip, int port,
6034 int address_family, int socket_flags, bool tcp_nodelay,
6035 bool ipv6_v6only, SocketOptions socket_options,
6036 BindOrConnect bind_or_connect, time_t timeout_sec = 0) {
6037 // Get address info
6038 const char *node = nullptr;
6039 struct addrinfo hints;
6040 struct addrinfo *result;
6041
6042 memset(&hints, 0, sizeof(struct addrinfo));
6043 hints.ai_socktype = SOCK_STREAM;
6044 hints.ai_protocol = IPPROTO_IP;
6045
6046 if (!ip.empty()) {
6047 node = ip.c_str();
6048 // Ask getaddrinfo to convert IP in c-string to address
6049 hints.ai_family = AF_UNSPEC;
6050 hints.ai_flags = AI_NUMERICHOST;
6051 } else {
6052 if (!host.empty()) { node = host.c_str(); }
6053 hints.ai_family = address_family;
6054 hints.ai_flags = socket_flags;
6055 }
6056
6057#if !defined(_WIN32) || defined(CPPHTTPLIB_HAVE_AFUNIX_H)
6058 if (hints.ai_family == AF_UNIX) {
6059 const auto addrlen = host.length();
6060 if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; }
6061
6062#ifdef SOCK_CLOEXEC
6063 auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC,
6064 hints.ai_protocol);
6065#else
6066 auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
6067#endif
6068
6069 if (sock != INVALID_SOCKET) {
6070 sockaddr_un addr{};
6071 addr.sun_family = AF_UNIX;
6072
6073 auto unescaped_host = unescape_abstract_namespace_unix_domain(host);
6074 std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path);
6075
6076 hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
6077 hints.ai_addrlen = static_cast<socklen_t>(
6078 sizeof(addr) - sizeof(addr.sun_path) + addrlen);
6079
6080#ifndef SOCK_CLOEXEC
6081#ifndef _WIN32
6082 fcntl(sock, F_SETFD, FD_CLOEXEC);
6083#endif
6084#endif
6085
6086 if (socket_options) { socket_options(sock); }
6087
6088#ifdef _WIN32
6089 // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so
6090 // remove the option.
6091 detail::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0);
6092#endif
6093
6094 bool dummy;
6095 if (!bind_or_connect(sock, hints, dummy)) {
6096 close_socket(sock);
6097 sock = INVALID_SOCKET;
6098 }
6099 }
6100 return sock;
6101 }
6102#endif
6103
6104 auto service = std::to_string(port);
6105
6106 if (getaddrinfo_with_timeout(node, service.c_str(), &hints, &result,
6107 timeout_sec)) {
6108#if defined __linux__ && !defined __ANDROID__
6109 res_init();
6110#endif
6111 return INVALID_SOCKET;
6112 }
6113 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
6114
6115 for (auto rp = result; rp; rp = rp->ai_next) {
6116 // Create a socket
6117#ifdef _WIN32
6118 auto sock =
6119 WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0,
6120 WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
6135 if (sock == INVALID_SOCKET) {
6136 sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
6137 }
6138#else
6139
6140#ifdef SOCK_CLOEXEC
6141 auto sock =
6142 socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol);
6143#else
6144 auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
6145#endif
6146
6147#endif
6148 if (sock == INVALID_SOCKET) { continue; }
6149
6150#if !defined _WIN32 && !defined SOCK_CLOEXEC
6151 if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
6152 close_socket(sock);
6153 continue;
6154 }
6155#endif
6156
6157 if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
6158
6159 if (rp->ai_family == AF_INET6) {
6160 set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0);
6161 }
6162
6163 if (socket_options) { socket_options(sock); }
6164
6165 // bind or connect
6166 auto quit = false;
6167 if (bind_or_connect(sock, *rp, quit)) { return sock; }
6168
6169 close_socket(sock);
6170
6171 if (quit) { break; }
6172 }
6173
6174 return INVALID_SOCKET;
6175}
6176
6177inline void set_nonblocking(socket_t sock, bool nonblocking) {
6178#ifdef _WIN32
6179 auto flags = nonblocking ? 1UL : 0UL;
6180 ioctlsocket(sock, FIONBIO, &flags);
6181#else
6182 auto flags = fcntl(sock, F_GETFL, 0);
6183 fcntl(sock, F_SETFL,
6184 nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
6185#endif
6186}
6187
6188inline bool is_connection_error() {
6189#ifdef _WIN32
6190 return WSAGetLastError() != WSAEWOULDBLOCK;
6191#else
6192 return errno != EINPROGRESS;
6193#endif
6194}
6195
6196inline bool bind_ip_address(socket_t sock, const std::string &host) {
6197 struct addrinfo hints;
6198 struct addrinfo *result;
6199
6200 memset(&hints, 0, sizeof(struct addrinfo));
6201 hints.ai_family = AF_UNSPEC;
6202 hints.ai_socktype = SOCK_STREAM;
6203 hints.ai_protocol = 0;
6204
6205 if (getaddrinfo_with_timeout(host.c_str(), "0", &hints, &result, 0)) {
6206 return false;
6207 }
6208
6209 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
6210
6211 auto ret = false;
6212 for (auto rp = result; rp; rp = rp->ai_next) {
6213 const auto &ai = *rp;
6214 if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
6215 ret = true;
6216 break;
6217 }
6218 }
6219
6220 return ret;
6221}
6222
6223#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__
6224#define USE_IF2IP
6225#endif
6226
6227#ifdef USE_IF2IP
6228inline std::string if2ip(int address_family, const std::string &ifn) {
6229 struct ifaddrs *ifap;
6230 getifaddrs(&ifap);
6231 auto se = detail::scope_exit([&] { freeifaddrs(ifap); });
6232
6233 std::string addr_candidate;
6234 for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
6235 if (ifa->ifa_addr && ifn == ifa->ifa_name &&
6236 (AF_UNSPEC == address_family ||
6237 ifa->ifa_addr->sa_family == address_family)) {
6238 if (ifa->ifa_addr->sa_family == AF_INET) {
6239 auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
6240 char buf[INET_ADDRSTRLEN];
6241 if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
6242 return std::string(buf, INET_ADDRSTRLEN);
6243 }
6244 } else if (ifa->ifa_addr->sa_family == AF_INET6) {
6245 auto sa = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr);
6246 if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
6247 char buf[INET6_ADDRSTRLEN] = {};
6248 if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) {
6249 // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL
6250 auto s6_addr_head = sa->sin6_addr.s6_addr[0];
6251 if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
6252 addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
6253 } else {
6254 return std::string(buf, INET6_ADDRSTRLEN);
6255 }
6256 }
6257 }
6258 }
6259 }
6260 }
6261 return addr_candidate;
6262}
6263#endif
6264
6266 const std::string &host, const std::string &ip, int port,
6267 int address_family, bool tcp_nodelay, bool ipv6_v6only,
6268 SocketOptions socket_options, time_t connection_timeout_sec,
6269 time_t connection_timeout_usec, time_t read_timeout_sec,
6270 time_t read_timeout_usec, time_t write_timeout_sec,
6271 time_t write_timeout_usec, const std::string &intf, Error &error) {
6272 auto sock = create_socket(
6273 host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only,
6274 std::move(socket_options),
6275 [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool {
6276 if (!intf.empty()) {
6277#ifdef USE_IF2IP
6278 auto ip_from_if = if2ip(address_family, intf);
6279 if (ip_from_if.empty()) { ip_from_if = intf; }
6280 if (!bind_ip_address(sock2, ip_from_if)) {
6281 error = Error::BindIPAddress;
6282 return false;
6283 }
6284#endif
6285 }
6286
6287 set_nonblocking(sock2, true);
6288
6289 auto ret =
6290 ::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
6291
6292 if (ret < 0) {
6293 if (is_connection_error()) {
6294 error = Error::Connection;
6295 return false;
6296 }
6297 error = wait_until_socket_is_ready(sock2, connection_timeout_sec,
6298 connection_timeout_usec);
6299 if (error != Error::Success) {
6300 if (error == Error::ConnectionTimeout) { quit = true; }
6301 return false;
6302 }
6303 }
6304
6305 set_nonblocking(sock2, false);
6306 set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec,
6307 read_timeout_usec);
6308 set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec,
6309 write_timeout_usec);
6310
6311 error = Error::Success;
6312 return true;
6313 },
6314 connection_timeout_sec); // Pass DNS timeout
6315
6316 if (sock != INVALID_SOCKET) {
6317 error = Error::Success;
6318 } else {
6319 if (error == Error::Success) { error = Error::Connection; }
6320 }
6321
6322 return sock;
6323}
6324
6325inline bool get_ip_and_port(const struct sockaddr_storage &addr,
6326 socklen_t addr_len, std::string &ip, int &port) {
6327 if (addr.ss_family == AF_INET) {
6328 port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
6329 } else if (addr.ss_family == AF_INET6) {
6330 port =
6331 ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
6332 } else {
6333 return false;
6334 }
6335
6336 std::array<char, NI_MAXHOST> ipstr{};
6337 if (getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
6338 ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
6339 0, NI_NUMERICHOST)) {
6340 return false;
6341 }
6342
6343 ip = ipstr.data();
6344 return true;
6345}
6346
6347inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) {
6348 struct sockaddr_storage addr;
6349 socklen_t addr_len = sizeof(addr);
6350 if (!getsockname(sock, reinterpret_cast<struct sockaddr *>(&addr),
6351 &addr_len)) {
6352 get_ip_and_port(addr, addr_len, ip, port);
6353 }
6354}
6355
6356inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
6357 struct sockaddr_storage addr;
6358 socklen_t addr_len = sizeof(addr);
6359
6360 if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
6361 &addr_len)) {
6362#ifndef _WIN32
6363 if (addr.ss_family == AF_UNIX) {
6364#if defined(__linux__)
6365 struct ucred ucred;
6366 socklen_t len = sizeof(ucred);
6367 if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) {
6368 port = ucred.pid;
6369 }
6370#elif defined(SOL_LOCAL) && defined(SO_PEERPID)
6371 pid_t pid;
6372 socklen_t len = sizeof(pid);
6373 if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) {
6374 port = pid;
6375 }
6376#endif
6377 return;
6378 }
6379#endif
6380 get_ip_and_port(addr, addr_len, ip, port);
6381 }
6382}
6383
6384inline constexpr unsigned int str2tag_core(const char *s, size_t l,
6385 unsigned int h) {
6386 return (l == 0)
6387 ? h
6388 : str2tag_core(
6389 s + 1, l - 1,
6390 // Unsets the 6 high bits of h, therefore no overflow happens
6391 (((std::numeric_limits<unsigned int>::max)() >> 6) &
6392 h * 33) ^
6393 static_cast<unsigned char>(*s));
6394}
6395
6396inline unsigned int str2tag(const std::string &s) {
6397 return str2tag_core(s.data(), s.size(), 0);
6398}
6399
6400namespace udl {
6401
6402inline constexpr unsigned int operator""_t(const char *s, size_t l) {
6403 return str2tag_core(s, l, 0);
6404}
6405
6406} // namespace udl
6407
6408inline std::string
6409find_content_type(const std::string &path,
6410 const std::map<std::string, std::string> &user_data,
6411 const std::string &default_content_type) {
6412 auto ext = file_extension(path);
6413
6414 auto it = user_data.find(ext);
6415 if (it != user_data.end()) { return it->second; }
6416
6417 using udl::operator""_t;
6418
6419 switch (str2tag(ext)) {
6420 default: return default_content_type;
6421
6422 case "css"_t: return "text/css";
6423 case "csv"_t: return "text/csv";
6424 case "htm"_t:
6425 case "html"_t: return "text/html";
6426 case "js"_t:
6427 case "mjs"_t: return "text/javascript";
6428 case "txt"_t: return "text/plain";
6429 case "vtt"_t: return "text/vtt";
6430
6431 case "apng"_t: return "image/apng";
6432 case "avif"_t: return "image/avif";
6433 case "bmp"_t: return "image/bmp";
6434 case "gif"_t: return "image/gif";
6435 case "png"_t: return "image/png";
6436 case "svg"_t: return "image/svg+xml";
6437 case "webp"_t: return "image/webp";
6438 case "ico"_t: return "image/x-icon";
6439 case "tif"_t: return "image/tiff";
6440 case "tiff"_t: return "image/tiff";
6441 case "jpg"_t:
6442 case "jpeg"_t: return "image/jpeg";
6443
6444 case "mp4"_t: return "video/mp4";
6445 case "mpeg"_t: return "video/mpeg";
6446 case "webm"_t: return "video/webm";
6447
6448 case "mp3"_t: return "audio/mp3";
6449 case "mpga"_t: return "audio/mpeg";
6450 case "weba"_t: return "audio/webm";
6451 case "wav"_t: return "audio/wave";
6452
6453 case "otf"_t: return "font/otf";
6454 case "ttf"_t: return "font/ttf";
6455 case "woff"_t: return "font/woff";
6456 case "woff2"_t: return "font/woff2";
6457
6458 case "7z"_t: return "application/x-7z-compressed";
6459 case "atom"_t: return "application/atom+xml";
6460 case "pdf"_t: return "application/pdf";
6461 case "json"_t: return "application/json";
6462 case "rss"_t: return "application/rss+xml";
6463 case "tar"_t: return "application/x-tar";
6464 case "xht"_t:
6465 case "xhtml"_t: return "application/xhtml+xml";
6466 case "xslt"_t: return "application/xslt+xml";
6467 case "xml"_t: return "application/xml";
6468 case "gz"_t: return "application/gzip";
6469 case "zip"_t: return "application/zip";
6470 case "wasm"_t: return "application/wasm";
6471 }
6472}
6473
6474inline std::string
6475extract_media_type(const std::string &content_type,
6476 std::map<std::string, std::string> *params = nullptr) {
6477 // Extract type/subtype from Content-Type value (RFC 2045)
6478 // e.g. "application/json; charset=utf-8" -> "application/json"
6479 auto media_type = content_type;
6480 auto semicolon_pos = media_type.find(';');
6481 if (semicolon_pos != std::string::npos) {
6482 auto param_str = media_type.substr(semicolon_pos + 1);
6483 media_type = media_type.substr(0, semicolon_pos);
6484
6485 if (params) {
6486 // Parse parameters: key=value pairs separated by ';'
6487 split(param_str.data(), param_str.data() + param_str.size(), ';',
6488 [&](const char *b, const char *e) {
6489 std::string key;
6490 std::string val;
6491 split(b, e, '=', [&](const char *b2, const char *e2) {
6492 if (key.empty()) {
6493 key.assign(b2, e2);
6494 } else {
6495 val.assign(b2, e2);
6496 }
6497 });
6498 if (!key.empty()) {
6499 params->emplace(trim_copy(key), trim_double_quotes_copy(val));
6500 }
6501 });
6502 }
6503 }
6504
6505 // Trim whitespace from media type
6506 return trim_copy(media_type);
6507}
6508
6509inline bool can_compress_content_type(const std::string &content_type) {
6510 using udl::operator""_t;
6511
6512 auto mime_type = extract_media_type(content_type);
6513 auto tag = str2tag(mime_type);
6514
6515 switch (tag) {
6516 case "image/svg+xml"_t:
6517 case "application/javascript"_t:
6518 case "application/x-javascript"_t:
6519 case "application/json"_t:
6520 case "application/ld+json"_t:
6521 case "application/xml"_t:
6522 case "application/xhtml+xml"_t:
6523 case "application/rss+xml"_t:
6524 case "application/atom+xml"_t:
6525 case "application/xslt+xml"_t:
6526 case "application/protobuf"_t: return true;
6527
6528 case "text/event-stream"_t: return false;
6529
6530 default: return !mime_type.rfind("text/", 0);
6531 }
6532}
6533
6534inline bool parse_quality(const char *b, const char *e, std::string &token,
6535 double &quality) {
6536 quality = 1.0;
6537 token.clear();
6538
6539 // Split on first ';': left = token name, right = parameters
6540 const char *params_b = nullptr;
6541 std::size_t params_len = 0;
6542
6543 divide(
6544 b, static_cast<std::size_t>(e - b), ';',
6545 [&](const char *lb, std::size_t llen, const char *rb, std::size_t rlen) {
6546 auto r = trim(lb, lb + llen, 0, llen);
6547 if (r.first < r.second) { token.assign(lb + r.first, lb + r.second); }
6548 params_b = rb;
6549 params_len = rlen;
6550 });
6551
6552 if (token.empty()) { return false; }
6553 if (params_len == 0) { return true; }
6554
6555 // Scan parameters for q= (stops on first match)
6556 bool invalid = false;
6557 split_find(params_b, params_b + params_len, ';',
6558 (std::numeric_limits<size_t>::max)(),
6559 [&](const char *pb, const char *pe) -> bool {
6560 // Match exactly "q=" or "Q=" (not "query=" etc.)
6561 auto len = static_cast<size_t>(pe - pb);
6562 if (len < 2) { return false; }
6563 if ((pb[0] != 'q' && pb[0] != 'Q') || pb[1] != '=') {
6564 return false;
6565 }
6566
6567 // Trim the value portion
6568 auto r = trim(pb, pe, 2, len);
6569 if (r.first >= r.second) {
6570 invalid = true;
6571 return true;
6572 }
6573
6574 double v = 0.0;
6575 auto res = from_chars(pb + r.first, pb + r.second, v);
6576 if (res.ec != std::errc{} || v < 0.0 || v > 1.0) {
6577 invalid = true;
6578 return true;
6579 }
6580 quality = v;
6581 return true;
6582 });
6583
6584 return !invalid;
6585}
6586
6587inline EncodingType encoding_type(const Request &req, const Response &res) {
6588 if (!can_compress_content_type(res.get_header_value("Content-Type"))) {
6589 return EncodingType::None;
6590 }
6591
6592 const auto &s = req.get_header_value("Accept-Encoding");
6593 if (s.empty()) { return EncodingType::None; }
6594
6595 // Single-pass: iterate tokens and track the best supported encoding.
6596 // Server preference breaks ties (br > gzip > zstd).
6598 double best_q = 0.0; // q=0 means "not acceptable"
6599
6600 // Server preference: Brotli > Gzip > Zstd (lower = more preferred)
6601 auto priority = [](EncodingType t) -> int {
6602 switch (t) {
6603 case EncodingType::Brotli: return 0;
6604 case EncodingType::Gzip: return 1;
6605 case EncodingType::Zstd: return 2;
6606 default: return 3;
6607 }
6608 };
6609
6610 std::string name;
6611 split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
6612 double quality = 1.0;
6613 if (!parse_quality(b, e, name, quality)) { return; }
6614 if (quality <= 0.0) { return; }
6615
6617#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6618 if (case_ignore::equal(name, "br")) { type = EncodingType::Brotli; }
6619#endif
6620#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6621 if (type == EncodingType::None && case_ignore::equal(name, "gzip")) {
6622 type = EncodingType::Gzip;
6623 }
6624#endif
6625#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6626 if (type == EncodingType::None && case_ignore::equal(name, "zstd")) {
6627 type = EncodingType::Zstd;
6628 }
6629#endif
6630
6631 if (type == EncodingType::None) { return; }
6632
6633 // Higher q-value wins; for equal q, server preference breaks ties
6634 if (quality > best_q ||
6635 (quality == best_q && priority(type) < priority(best))) {
6636 best_q = quality;
6637 best = type;
6638 }
6639 });
6640
6641 return best;
6642}
6643
6644inline bool nocompressor::compress(const char *data, size_t data_length,
6645 bool /*last*/, Callback callback) {
6646 if (!data_length) { return true; }
6647 return callback(data, data_length);
6648}
6649
6650#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6651inline gzip_compressor::gzip_compressor() {
6652 std::memset(&strm_, 0, sizeof(strm_));
6653 strm_.zalloc = Z_NULL;
6654 strm_.zfree = Z_NULL;
6655 strm_.opaque = Z_NULL;
6656
6657 is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
6658 Z_DEFAULT_STRATEGY) == Z_OK;
6659}
6660
6661inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); }
6662
6663inline bool gzip_compressor::compress(const char *data, size_t data_length,
6664 bool last, Callback callback) {
6665 assert(is_valid_);
6666
6667 do {
6668 constexpr size_t max_avail_in =
6669 (std::numeric_limits<decltype(strm_.avail_in)>::max)();
6670
6671 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
6672 (std::min)(data_length, max_avail_in));
6673 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
6674
6675 data_length -= strm_.avail_in;
6676 data += strm_.avail_in;
6677
6678 auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
6679 auto ret = Z_OK;
6680
6681 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6682 do {
6683 strm_.avail_out = static_cast<uInt>(buff.size());
6684 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
6685
6686 ret = deflate(&strm_, flush);
6687 if (ret == Z_STREAM_ERROR) { return false; }
6688
6689 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
6690 return false;
6691 }
6692 } while (strm_.avail_out == 0);
6693
6694 assert((flush == Z_FINISH && ret == Z_STREAM_END) ||
6695 (flush == Z_NO_FLUSH && ret == Z_OK));
6696 assert(strm_.avail_in == 0);
6697 } while (data_length > 0);
6698
6699 return true;
6700}
6701
6702inline gzip_decompressor::gzip_decompressor() {
6703 std::memset(&strm_, 0, sizeof(strm_));
6704 strm_.zalloc = Z_NULL;
6705 strm_.zfree = Z_NULL;
6706 strm_.opaque = Z_NULL;
6707
6708 // 15 is the value of wbits, which should be at the maximum possible value
6709 // to ensure that any gzip stream can be decoded. The offset of 32 specifies
6710 // that the stream type should be automatically detected either gzip or
6711 // deflate.
6712 is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
6713}
6714
6715inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); }
6716
6717inline bool gzip_decompressor::is_valid() const { return is_valid_; }
6718
6719inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
6720 Callback callback) {
6721 assert(is_valid_);
6722
6723 auto ret = Z_OK;
6724
6725 do {
6726 constexpr size_t max_avail_in =
6727 (std::numeric_limits<decltype(strm_.avail_in)>::max)();
6728
6729 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
6730 (std::min)(data_length, max_avail_in));
6731 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
6732
6733 data_length -= strm_.avail_in;
6734 data += strm_.avail_in;
6735
6736 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6737 while (strm_.avail_in > 0 && ret == Z_OK) {
6738 strm_.avail_out = static_cast<uInt>(buff.size());
6739 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
6740
6741 ret = inflate(&strm_, Z_NO_FLUSH);
6742
6743 assert(ret != Z_STREAM_ERROR);
6744 switch (ret) {
6745 case Z_NEED_DICT:
6746 case Z_DATA_ERROR:
6747 case Z_MEM_ERROR: inflateEnd(&strm_); return false;
6748 }
6749
6750 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
6751 return false;
6752 }
6753 }
6754
6755 if (ret != Z_OK && ret != Z_STREAM_END) { return false; }
6756
6757 } while (data_length > 0);
6758
6759 return true;
6760}
6761#endif
6762
6763#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6764inline brotli_compressor::brotli_compressor() {
6765 state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
6766}
6767
6768inline brotli_compressor::~brotli_compressor() {
6769 BrotliEncoderDestroyInstance(state_);
6770}
6771
6772inline bool brotli_compressor::compress(const char *data, size_t data_length,
6773 bool last, Callback callback) {
6774 std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6775
6776 auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
6777 auto available_in = data_length;
6778 auto next_in = reinterpret_cast<const uint8_t *>(data);
6779
6780 for (;;) {
6781 if (last) {
6782 if (BrotliEncoderIsFinished(state_)) { break; }
6783 } else {
6784 if (!available_in) { break; }
6785 }
6786
6787 auto available_out = buff.size();
6788 auto next_out = buff.data();
6789
6790 if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in,
6791 &available_out, &next_out, nullptr)) {
6792 return false;
6793 }
6794
6795 auto output_bytes = buff.size() - available_out;
6796 if (output_bytes) {
6797 callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
6798 }
6799 }
6800
6801 return true;
6802}
6803
6804inline brotli_decompressor::brotli_decompressor() {
6805 decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
6806 decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
6807 : BROTLI_DECODER_RESULT_ERROR;
6808}
6809
6810inline brotli_decompressor::~brotli_decompressor() {
6811 if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
6812}
6813
6814inline bool brotli_decompressor::is_valid() const { return decoder_s; }
6815
6816inline bool brotli_decompressor::decompress(const char *data,
6817 size_t data_length,
6818 Callback callback) {
6819 if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
6820 decoder_r == BROTLI_DECODER_RESULT_ERROR) {
6821 return 0;
6822 }
6823
6824 auto next_in = reinterpret_cast<const uint8_t *>(data);
6825 size_t avail_in = data_length;
6826 size_t total_out;
6827
6828 decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
6829
6830 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6831 while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
6832 char *next_out = buff.data();
6833 size_t avail_out = buff.size();
6834
6835 decoder_r = BrotliDecoderDecompressStream(
6836 decoder_s, &avail_in, &next_in, &avail_out,
6837 reinterpret_cast<uint8_t **>(&next_out), &total_out);
6838
6839 if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
6840
6841 if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
6842 }
6843
6844 return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
6845 decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
6846}
6847#endif
6848
6849#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6850inline zstd_compressor::zstd_compressor() {
6851 ctx_ = ZSTD_createCCtx();
6852 ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast);
6853}
6854
6855inline zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); }
6856
6857inline bool zstd_compressor::compress(const char *data, size_t data_length,
6858 bool last, Callback callback) {
6859 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6860
6861 ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue;
6862 ZSTD_inBuffer input = {data, data_length, 0};
6863
6864 bool finished;
6865 do {
6866 ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
6867 size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode);
6868
6869 if (ZSTD_isError(remaining)) { return false; }
6870
6871 if (!callback(buff.data(), output.pos)) { return false; }
6872
6873 finished = last ? (remaining == 0) : (input.pos == input.size);
6874
6875 } while (!finished);
6876
6877 return true;
6878}
6879
6880inline zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); }
6881
6882inline zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); }
6883
6884inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; }
6885
6886inline bool zstd_decompressor::decompress(const char *data, size_t data_length,
6887 Callback callback) {
6888 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6889 ZSTD_inBuffer input = {data, data_length, 0};
6890
6891 while (input.pos < input.size) {
6892 ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
6893 size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input);
6894
6895 if (ZSTD_isError(remaining)) { return false; }
6896
6897 if (!callback(buff.data(), output.pos)) { return false; }
6898 }
6899
6900 return true;
6901}
6902#endif
6903
6904inline std::unique_ptr<decompressor>
6905create_decompressor(const std::string &encoding) {
6906 std::unique_ptr<decompressor> decompressor;
6907
6908 if (encoding == "gzip" || encoding == "deflate") {
6909#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6911#endif
6912 } else if (encoding.find("br") != std::string::npos) {
6913#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6915#endif
6916 } else if (encoding == "zstd" || encoding.find("zstd") != std::string::npos) {
6917#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6919#endif
6920 }
6921
6922 return decompressor;
6923}
6924
6925// Returns the best available compressor and its Content-Encoding name.
6926// Priority: Brotli > Gzip > Zstd (matches server-side preference).
6927inline std::pair<std::unique_ptr<compressor>, const char *>
6929#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6931#elif defined(CPPHTTPLIB_ZLIB_SUPPORT)
6932 return {detail::make_unique<gzip_compressor>(), "gzip"};
6933#elif defined(CPPHTTPLIB_ZSTD_SUPPORT)
6934 return {detail::make_unique<zstd_compressor>(), "zstd"};
6935#else
6936 return {nullptr, nullptr};
6937#endif
6938}
6939
6940inline bool is_prohibited_header_name(const std::string &name) {
6941 using udl::operator""_t;
6942
6943 switch (str2tag(name)) {
6944 case "REMOTE_ADDR"_t:
6945 case "REMOTE_PORT"_t:
6946 case "LOCAL_ADDR"_t:
6947 case "LOCAL_PORT"_t: return true;
6948 default: return false;
6949 }
6950}
6951
6952inline bool has_header(const Headers &headers, const std::string &key) {
6953 if (is_prohibited_header_name(key)) { return false; }
6954 return headers.find(key) != headers.end();
6955}
6956
6957inline const char *get_header_value(const Headers &headers,
6958 const std::string &key, const char *def,
6959 size_t id) {
6960 if (is_prohibited_header_name(key)) {
6961#ifndef CPPHTTPLIB_NO_EXCEPTIONS
6962 std::string msg = "Prohibited header name '" + key + "' is specified.";
6963 throw std::invalid_argument(msg);
6964#else
6965 return "";
6966#endif
6967 }
6968
6969 auto rng = headers.equal_range(key);
6970 auto it = rng.first;
6971 std::advance(it, static_cast<ssize_t>(id));
6972 if (it != rng.second) { return it->second.c_str(); }
6973 return def;
6974}
6975
6976inline bool read_headers(Stream &strm, Headers &headers) {
6977 const auto bufsiz = 2048;
6978 char buf[bufsiz];
6979 stream_line_reader line_reader(strm, buf, bufsiz);
6980
6981 size_t header_count = 0;
6982
6983 for (;;) {
6984 if (!line_reader.getline()) { return false; }
6985
6986 // Check if the line ends with CRLF.
6987 auto line_terminator_len = 2;
6988 if (line_reader.end_with_crlf()) {
6989 // Blank line indicates end of headers.
6990 if (line_reader.size() == 2) { break; }
6991 } else {
6992#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
6993 // Blank line indicates end of headers.
6994 if (line_reader.size() == 1) { break; }
6995 line_terminator_len = 1;
6996#else
6997 continue; // Skip invalid line.
6998#endif
6999 }
7000
7001 if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
7002
7003 // Check header count limit
7004 if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
7005
7006 // Exclude line terminator
7007 auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
7008
7009 if (!parse_header(line_reader.ptr(), end,
7010 [&](const std::string &key, const std::string &val) {
7011 headers.emplace(key, val);
7012 })) {
7013 return false;
7014 }
7015
7016 header_count++;
7017 }
7018
7019 // RFC 9110 Section 8.6: Reject requests with multiple Content-Length
7020 // headers that have different values to prevent request smuggling.
7021 auto cl_range = headers.equal_range("Content-Length");
7022 if (cl_range.first != cl_range.second) {
7023 const auto &first_val = cl_range.first->second;
7024 for (auto it = std::next(cl_range.first); it != cl_range.second; ++it) {
7025 if (it->second != first_val) { return false; }
7026 }
7027 }
7028
7029 return true;
7030}
7031
7033 const std::string &expected_accept,
7034 std::string &selected_subprotocol) {
7035 // Read status line
7036 const auto bufsiz = 2048;
7037 char buf[bufsiz];
7038 stream_line_reader line_reader(strm, buf, bufsiz);
7039 if (!line_reader.getline()) { return false; }
7040
7041 // Check for "HTTP/1.1 101"
7042 auto line = std::string(line_reader.ptr(), line_reader.size());
7043 if (line.find("HTTP/1.1 101") == std::string::npos) { return false; }
7044
7045 // Parse headers using existing read_headers
7046 Headers headers;
7047 if (!read_headers(strm, headers)) { return false; }
7048
7049 // Verify Upgrade: websocket (case-insensitive)
7050 auto upgrade_it = headers.find("Upgrade");
7051 if (upgrade_it == headers.end()) { return false; }
7052 auto upgrade_val = case_ignore::to_lower(upgrade_it->second);
7053 if (upgrade_val != "websocket") { return false; }
7054
7055 // Verify Connection header contains "Upgrade" (case-insensitive)
7056 auto connection_it = headers.find("Connection");
7057 if (connection_it == headers.end()) { return false; }
7058 auto connection_val = case_ignore::to_lower(connection_it->second);
7059 if (connection_val.find("upgrade") == std::string::npos) { return false; }
7060
7061 // Verify Sec-WebSocket-Accept header value
7062 auto it = headers.find("Sec-WebSocket-Accept");
7063 if (it == headers.end() || it->second != expected_accept) { return false; }
7064
7065 // Extract negotiated subprotocol
7066 auto proto_it = headers.find("Sec-WebSocket-Protocol");
7067 if (proto_it != headers.end()) { selected_subprotocol = proto_it->second; }
7068
7069 return true;
7070}
7071
7073 Success, // Successfully read the content
7074 PayloadTooLarge, // The content exceeds the specified payload limit
7075 Error // An error occurred while reading the content
7076};
7077
7079 Stream &strm, size_t len, DownloadProgress progress,
7081 size_t payload_max_length = (std::numeric_limits<size_t>::max)()) {
7082 char buf[CPPHTTPLIB_RECV_BUFSIZ];
7083
7085 br.stream = &strm;
7086 br.has_content_length = true;
7087 br.content_length = len;
7088 br.payload_max_length = payload_max_length;
7089 br.chunked = false;
7090 br.bytes_read = 0;
7092
7093 size_t r = 0;
7094 while (r < len) {
7095 auto read_len = static_cast<size_t>(len - r);
7096 auto to_read = (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ);
7097 auto n = detail::read_body_content(&strm, br, buf, to_read);
7098 if (n <= 0) {
7099 // Check if it was a payload size error
7102 }
7104 }
7105
7106 if (!out(buf, static_cast<size_t>(n), r, len)) {
7108 }
7109 r += static_cast<size_t>(n);
7110
7111 if (progress) {
7112 if (!progress(r, len)) { return ReadContentResult::Error; }
7113 }
7114 }
7115
7117}
7118
7119inline ReadContentResult
7120read_content_without_length(Stream &strm, size_t payload_max_length,
7122 char buf[CPPHTTPLIB_RECV_BUFSIZ];
7123 size_t r = 0;
7124 for (;;) {
7125 auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
7126 if (n == 0) { return ReadContentResult::Success; }
7127 if (n < 0) { return ReadContentResult::Error; }
7128
7129 // Check if adding this data would exceed the payload limit
7130 if (r > payload_max_length ||
7131 payload_max_length - r < static_cast<size_t>(n)) {
7133 }
7134
7135 if (!out(buf, static_cast<size_t>(n), r, 0)) {
7137 }
7138 r += static_cast<size_t>(n);
7139 }
7140
7142}
7143
7144template <typename T>
7146 size_t payload_max_length,
7148 detail::ChunkedDecoder dec(strm);
7149
7150 char buf[CPPHTTPLIB_RECV_BUFSIZ];
7151 size_t total_len = 0;
7152
7153 for (;;) {
7154 size_t chunk_offset = 0;
7155 size_t chunk_total = 0;
7156 auto n = dec.read_payload(buf, sizeof(buf), chunk_offset, chunk_total);
7157 if (n < 0) { return ReadContentResult::Error; }
7158
7159 if (n == 0) {
7160 if (!dec.parse_trailers_into(x.trailers, x.headers)) {
7162 }
7164 }
7165
7166 if (total_len > payload_max_length ||
7167 payload_max_length - total_len < static_cast<size_t>(n)) {
7169 }
7170
7171 if (!out(buf, static_cast<size_t>(n), chunk_offset, chunk_total)) {
7173 }
7174
7175 total_len += static_cast<size_t>(n);
7176 }
7177}
7178
7179inline bool is_chunked_transfer_encoding(const Headers &headers) {
7180 return case_ignore::equal(
7181 get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
7182}
7183
7184template <typename T, typename U>
7185bool prepare_content_receiver(T &x, int &status,
7187 bool decompress, size_t payload_max_length,
7188 bool &exceed_payload_max_length, U callback) {
7189 if (decompress) {
7190 std::string encoding = x.get_header_value("Content-Encoding");
7191 std::unique_ptr<decompressor> decompressor;
7192
7193 if (!encoding.empty()) {
7195 if (!decompressor) {
7196 // Unsupported encoding or no support compiled in
7198 return false;
7199 }
7200 }
7201
7202 if (decompressor) {
7203 if (decompressor->is_valid()) {
7204 size_t decompressed_size = 0;
7205 ContentReceiverWithProgress out = [&](const char *buf, size_t n,
7206 size_t off, size_t len) {
7207 return decompressor->decompress(
7208 buf, n, [&](const char *buf2, size_t n2) {
7209 // Guard against zip-bomb: check
7210 // decompressed size against limit.
7211 if (payload_max_length > 0 &&
7212 (decompressed_size >= payload_max_length ||
7213 n2 > payload_max_length - decompressed_size)) {
7214 exceed_payload_max_length = true;
7215 return false;
7216 }
7217 decompressed_size += n2;
7218 return receiver(buf2, n2, off, len);
7219 });
7220 };
7221 return callback(std::move(out));
7222 } else {
7224 return false;
7225 }
7226 }
7227 }
7228
7229 ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off,
7230 size_t len) {
7231 return receiver(buf, n, off, len);
7232 };
7233 return callback(std::move(out));
7234}
7235
7236template <typename T>
7237bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
7238 DownloadProgress progress,
7239 ContentReceiverWithProgress receiver, bool decompress) {
7240 bool exceed_payload_max_length = false;
7242 x, status, std::move(receiver), decompress, payload_max_length,
7243 exceed_payload_max_length, [&](const ContentReceiverWithProgress &out) {
7244 auto ret = true;
7245 // Note: exceed_payload_max_length may also be set by the decompressor
7246 // wrapper in prepare_content_receiver when the decompressed payload
7247 // size exceeds the limit.
7248
7249 if (is_chunked_transfer_encoding(x.headers)) {
7250 auto result = read_content_chunked(strm, x, payload_max_length, out);
7251 if (result == ReadContentResult::Success) {
7252 ret = true;
7253 } else if (result == ReadContentResult::PayloadTooLarge) {
7254 exceed_payload_max_length = true;
7255 ret = false;
7256 } else {
7257 ret = false;
7258 }
7259 } else if (!has_header(x.headers, "Content-Length")) {
7260 auto result =
7261 read_content_without_length(strm, payload_max_length, out);
7262 if (result == ReadContentResult::Success) {
7263 ret = true;
7264 } else if (result == ReadContentResult::PayloadTooLarge) {
7265 exceed_payload_max_length = true;
7266 ret = false;
7267 } else {
7268 ret = false;
7269 }
7270 } else {
7271 auto is_invalid_value = false;
7272 auto len = get_header_value_u64(x.headers, "Content-Length",
7273 (std::numeric_limits<size_t>::max)(),
7274 0, is_invalid_value);
7275
7276 if (is_invalid_value) {
7277 ret = false;
7278 } else if (len > 0) {
7279 auto result = read_content_with_length(
7280 strm, len, std::move(progress), out, payload_max_length);
7281 ret = (result == ReadContentResult::Success);
7282 if (result == ReadContentResult::PayloadTooLarge) {
7283 exceed_payload_max_length = true;
7284 }
7285 }
7286 }
7287
7288 if (!ret) {
7289 status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413
7291 }
7292 return ret;
7293 });
7294}
7295
7296inline ssize_t write_request_line(Stream &strm, const std::string &method,
7297 const std::string &path) {
7298 std::string s = method;
7299 s += ' ';
7300 s += path;
7301 s += " HTTP/1.1\r\n";
7302 return strm.write(s.data(), s.size());
7303}
7304
7305inline ssize_t write_response_line(Stream &strm, int status) {
7306 std::string s = "HTTP/1.1 ";
7307 s += std::to_string(status);
7308 s += ' ';
7309 s += httplib::status_message(status);
7310 s += "\r\n";
7311 return strm.write(s.data(), s.size());
7312}
7313
7314inline ssize_t write_headers(Stream &strm, const Headers &headers) {
7315 ssize_t write_len = 0;
7316 for (const auto &x : headers) {
7317 std::string s;
7318 s = x.first;
7319 s += ": ";
7320 s += x.second;
7321 s += "\r\n";
7322
7323 auto len = strm.write(s.data(), s.size());
7324 if (len < 0) { return len; }
7325 write_len += len;
7326 }
7327 auto len = strm.write("\r\n");
7328 if (len < 0) { return len; }
7329 write_len += len;
7330 return write_len;
7331}
7332
7333inline bool write_data(Stream &strm, const char *d, size_t l) {
7334 size_t offset = 0;
7335 while (offset < l) {
7336 auto length = strm.write(d + offset, l - offset);
7337 if (length < 0) { return false; }
7338 offset += static_cast<size_t>(length);
7339 }
7340 return true;
7341}
7342
7343template <typename T>
7345 const ContentProvider &content_provider,
7346 size_t offset, size_t length,
7347 T is_shutting_down,
7348 const UploadProgress &upload_progress,
7349 Error &error) {
7350 size_t end_offset = offset + length;
7351 size_t start_offset = offset;
7352 auto ok = true;
7353 DataSink data_sink;
7354
7355 data_sink.write = [&](const char *d, size_t l) -> bool {
7356 if (ok) {
7357 if (write_data(strm, d, l)) {
7358 offset += l;
7359
7360 if (upload_progress && length > 0) {
7361 size_t current_written = offset - start_offset;
7362 if (!upload_progress(current_written, length)) {
7363 ok = false;
7364 return false;
7365 }
7366 }
7367 } else {
7368 ok = false;
7369 }
7370 }
7371 return ok;
7372 };
7373
7374 data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7375
7376 while (offset < end_offset && !is_shutting_down()) {
7377 if (!strm.wait_writable() || !strm.is_peer_alive()) {
7378 error = Error::Write;
7379 return false;
7380 } else if (!content_provider(offset, end_offset - offset, data_sink)) {
7381 error = Error::Canceled;
7382 return false;
7383 } else if (!ok) {
7384 error = Error::Write;
7385 return false;
7386 }
7387 }
7388
7389 if (offset < end_offset) { // exited due to is_shutting_down(), not completion
7390 error = Error::Write;
7391 return false;
7392 }
7393
7394 error = Error::Success;
7395 return true;
7396}
7397
7398template <typename T>
7399inline bool write_content(Stream &strm, const ContentProvider &content_provider,
7400 size_t offset, size_t length, T is_shutting_down,
7401 Error &error) {
7402 return write_content_with_progress<T>(strm, content_provider, offset, length,
7403 is_shutting_down, nullptr, error);
7404}
7405
7406template <typename T>
7407inline bool write_content(Stream &strm, const ContentProvider &content_provider,
7408 size_t offset, size_t length,
7409 const T &is_shutting_down) {
7410 auto error = Error::Success;
7411 return write_content(strm, content_provider, offset, length, is_shutting_down,
7412 error);
7413}
7414
7415template <typename T>
7416inline bool
7418 const ContentProvider &content_provider,
7419 const T &is_shutting_down) {
7420 size_t offset = 0;
7421 auto data_available = true;
7422 auto ok = true;
7423 DataSink data_sink;
7424
7425 data_sink.write = [&](const char *d, size_t l) -> bool {
7426 if (ok) {
7427 offset += l;
7428 if (!write_data(strm, d, l)) { ok = false; }
7429 }
7430 return ok;
7431 };
7432
7433 data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7434
7435 data_sink.done = [&](void) { data_available = false; };
7436
7437 while (data_available && !is_shutting_down()) {
7438 if (!strm.wait_writable() || !strm.is_peer_alive()) {
7439 return false;
7440 } else if (!content_provider(offset, 0, data_sink)) {
7441 return false;
7442 } else if (!ok) {
7443 return false;
7444 }
7445 }
7446 return !data_available; // true only if done() was called, false if shutting
7447 // down
7448}
7449
7450template <typename T, typename U>
7451inline bool
7452write_content_chunked(Stream &strm, const ContentProvider &content_provider,
7453 const T &is_shutting_down, U &compressor, Error &error) {
7454 size_t offset = 0;
7455 auto data_available = true;
7456 auto ok = true;
7457 DataSink data_sink;
7458
7459 data_sink.write = [&](const char *d, size_t l) -> bool {
7460 if (ok) {
7461 data_available = l > 0;
7462 offset += l;
7463
7464 std::string payload;
7465 if (compressor.compress(d, l, false,
7466 [&](const char *data, size_t data_len) {
7467 payload.append(data, data_len);
7468 return true;
7469 })) {
7470 if (!payload.empty()) {
7471 // Emit chunked response header and footer for each chunk
7472 auto chunk =
7473 from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
7474 if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
7475 }
7476 } else {
7477 ok = false;
7478 }
7479 }
7480 return ok;
7481 };
7482
7483 data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7484
7485 auto done_with_trailer = [&](const Headers *trailer) {
7486 if (!ok) { return; }
7487
7488 data_available = false;
7489
7490 std::string payload;
7491 if (!compressor.compress(nullptr, 0, true,
7492 [&](const char *data, size_t data_len) {
7493 payload.append(data, data_len);
7494 return true;
7495 })) {
7496 ok = false;
7497 return;
7498 }
7499
7500 if (!payload.empty()) {
7501 // Emit chunked response header and footer for each chunk
7502 auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
7503 if (!write_data(strm, chunk.data(), chunk.size())) {
7504 ok = false;
7505 return;
7506 }
7507 }
7508
7509 constexpr const char done_marker[] = "0\r\n";
7510 if (!write_data(strm, done_marker, str_len(done_marker))) { ok = false; }
7511
7512 // Trailer
7513 if (trailer) {
7514 for (const auto &kv : *trailer) {
7515 std::string field_line = kv.first + ": " + kv.second + "\r\n";
7516 if (!write_data(strm, field_line.data(), field_line.size())) {
7517 ok = false;
7518 }
7519 }
7520 }
7521
7522 constexpr const char crlf[] = "\r\n";
7523 if (!write_data(strm, crlf, str_len(crlf))) { ok = false; }
7524 };
7525
7526 data_sink.done = [&](void) { done_with_trailer(nullptr); };
7527
7528 data_sink.done_with_trailer = [&](const Headers &trailer) {
7529 done_with_trailer(&trailer);
7530 };
7531
7532 while (data_available && !is_shutting_down()) {
7533 if (!strm.wait_writable() || !strm.is_peer_alive()) {
7534 error = Error::Write;
7535 return false;
7536 } else if (!content_provider(offset, 0, data_sink)) {
7537 error = Error::Canceled;
7538 return false;
7539 } else if (!ok) {
7540 error = Error::Write;
7541 return false;
7542 }
7543 }
7544
7545 if (data_available) { // exited due to is_shutting_down(), not done()
7546 error = Error::Write;
7547 return false;
7548 }
7549
7550 error = Error::Success;
7551 return true;
7552}
7553
7554template <typename T, typename U>
7556 const ContentProvider &content_provider,
7557 const T &is_shutting_down, U &compressor) {
7558 auto error = Error::Success;
7559 return write_content_chunked(strm, content_provider, is_shutting_down,
7560 compressor, error);
7561}
7562
7563template <typename T>
7564inline bool redirect(T &cli, Request &req, Response &res,
7565 const std::string &path, const std::string &location,
7566 Error &error) {
7567 Request new_req = req;
7568 new_req.path = path;
7569 new_req.redirect_count_ -= 1;
7570
7571 if (res.status == StatusCode::SeeOther_303 &&
7572 (req.method != "GET" && req.method != "HEAD")) {
7573 new_req.method = "GET";
7574 new_req.body.clear();
7575 new_req.headers.clear();
7576 }
7577
7578 Response new_res;
7579
7580 auto ret = cli.send(new_req, new_res, error);
7581 if (ret) {
7582 req = std::move(new_req);
7583 res = std::move(new_res);
7584
7585 if (res.location.empty()) { res.location = location; }
7586 }
7587 return ret;
7588}
7589
7590inline std::string params_to_query_str(const Params &params) {
7591 std::string query;
7592
7593 for (auto it = params.begin(); it != params.end(); ++it) {
7594 if (it != params.begin()) { query += '&'; }
7595 query += encode_query_component(it->first);
7596 query += '=';
7597 query += encode_query_component(it->second);
7598 }
7599 return query;
7600}
7601
7602inline void parse_query_text(const char *data, std::size_t size,
7603 Params &params) {
7604 std::set<std::string> cache;
7605 split(data, data + size, '&', [&](const char *b, const char *e) {
7606 std::string kv(b, e);
7607 if (cache.find(kv) != cache.end()) { return; }
7608 cache.insert(std::move(kv));
7609
7610 std::string key;
7611 std::string val;
7612 divide(b, static_cast<std::size_t>(e - b), '=',
7613 [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data,
7614 std::size_t rhs_size) {
7615 key.assign(lhs_data, lhs_size);
7616 val.assign(rhs_data, rhs_size);
7617 });
7618
7619 if (!key.empty()) {
7620 params.emplace(decode_query_component(key), decode_query_component(val));
7621 }
7622 });
7623}
7624
7625inline void parse_query_text(const std::string &s, Params &params) {
7626 parse_query_text(s.data(), s.size(), params);
7627}
7628
7629// Normalize a query string by decoding and re-encoding each key/value pair
7630// while preserving the original parameter order. This avoids double-encoding
7631// and ensures consistent encoding without reordering (unlike Params which
7632// uses std::multimap and sorts keys).
7633inline std::string normalize_query_string(const std::string &query) {
7634 std::string result;
7635 split(query.data(), query.data() + query.size(), '&',
7636 [&](const char *b, const char *e) {
7637 std::string key;
7638 std::string val;
7639 divide(b, static_cast<std::size_t>(e - b), '=',
7640 [&](const char *lhs_data, std::size_t lhs_size,
7641 const char *rhs_data, std::size_t rhs_size) {
7642 key.assign(lhs_data, lhs_size);
7643 val.assign(rhs_data, rhs_size);
7644 });
7645
7646 if (!key.empty()) {
7647 auto dec_key = decode_query_component(key);
7648 auto dec_val = decode_query_component(val);
7649
7650 if (!result.empty()) { result += '&'; }
7651 result += encode_query_component(dec_key);
7652 if (!val.empty() || std::find(b, e, '=') != e) {
7653 result += '=';
7654 result += encode_query_component(dec_val);
7655 }
7656 }
7657 });
7658 return result;
7659}
7660
7661inline bool parse_multipart_boundary(const std::string &content_type,
7662 std::string &boundary) {
7663 std::map<std::string, std::string> params;
7664 extract_media_type(content_type, &params);
7665 auto it = params.find("boundary");
7666 if (it == params.end()) { return false; }
7667 boundary = it->second;
7668 return !boundary.empty();
7669}
7670
7671inline void parse_disposition_params(const std::string &s, Params &params) {
7672 std::set<std::string> cache;
7673 split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) {
7674 std::string kv(b, e);
7675 if (cache.find(kv) != cache.end()) { return; }
7676 cache.insert(kv);
7677
7678 std::string key;
7679 std::string val;
7680 split(b, e, '=', [&](const char *b2, const char *e2) {
7681 if (key.empty()) {
7682 key.assign(b2, e2);
7683 } else {
7684 val.assign(b2, e2);
7685 }
7686 });
7687
7688 if (!key.empty()) {
7689 params.emplace(trim_double_quotes_copy((key)),
7690 trim_double_quotes_copy((val)));
7691 }
7692 });
7693}
7694
7695#ifdef CPPHTTPLIB_NO_EXCEPTIONS
7696inline bool parse_range_header(const std::string &s, Ranges &ranges) {
7697#else
7698inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
7699#endif
7700 auto is_valid = [](const std::string &str) {
7701 return std::all_of(str.cbegin(), str.cend(),
7702 [](unsigned char c) { return std::isdigit(c); });
7703 };
7704
7705 if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) {
7706 const auto pos = static_cast<size_t>(6);
7707 const auto len = static_cast<size_t>(s.size() - 6);
7708 auto all_valid_ranges = true;
7709 split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
7710 if (!all_valid_ranges) { return; }
7711
7712 const auto it = std::find(b, e, '-');
7713 if (it == e) {
7714 all_valid_ranges = false;
7715 return;
7716 }
7717
7718 const auto lhs = std::string(b, it);
7719 const auto rhs = std::string(it + 1, e);
7720 if (!is_valid(lhs) || !is_valid(rhs)) {
7721 all_valid_ranges = false;
7722 return;
7723 }
7724
7725 ssize_t first = -1;
7726 if (!lhs.empty()) {
7727 ssize_t v;
7728 auto res = detail::from_chars(lhs.data(), lhs.data() + lhs.size(), v);
7729 if (res.ec == std::errc{}) { first = v; }
7730 }
7731
7732 ssize_t last = -1;
7733 if (!rhs.empty()) {
7734 ssize_t v;
7735 auto res = detail::from_chars(rhs.data(), rhs.data() + rhs.size(), v);
7736 if (res.ec == std::errc{}) { last = v; }
7737 }
7738
7739 if ((first == -1 && last == -1) ||
7740 (first != -1 && last != -1 && first > last)) {
7741 all_valid_ranges = false;
7742 return;
7743 }
7744
7745 ranges.emplace_back(first, last);
7746 });
7747 return all_valid_ranges && !ranges.empty();
7748 }
7749 return false;
7750#ifdef CPPHTTPLIB_NO_EXCEPTIONS
7751}
7752#else
7753} catch (...) { return false; }
7754#endif
7755
7756inline bool parse_accept_header(const std::string &s,
7757 std::vector<std::string> &content_types) {
7758 content_types.clear();
7759
7760 // Empty string is considered valid (no preference)
7761 if (s.empty()) { return true; }
7762
7763 // Check for invalid patterns: leading/trailing commas or consecutive commas
7764 if (s.front() == ',' || s.back() == ',' ||
7765 s.find(",,") != std::string::npos) {
7766 return false;
7767 }
7768
7769 struct AcceptEntry {
7770 std::string media_type;
7771 double quality;
7772 int order;
7773 };
7774
7775 std::vector<AcceptEntry> entries;
7776 int order = 0;
7777 bool has_invalid_entry = false;
7778
7779 // Split by comma and parse each entry
7780 split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
7781 std::string entry(b, e);
7782 entry = trim_copy(entry);
7783
7784 if (entry.empty()) {
7785 has_invalid_entry = true;
7786 return;
7787 }
7788
7789 AcceptEntry accept_entry;
7790 accept_entry.order = order++;
7791
7792 if (!parse_quality(entry.data(), entry.data() + entry.size(),
7793 accept_entry.media_type, accept_entry.quality)) {
7794 has_invalid_entry = true;
7795 return;
7796 }
7797
7798 // Remove additional parameters from media type
7799 accept_entry.media_type = extract_media_type(accept_entry.media_type);
7800
7801 // Basic validation of media type format
7802 if (accept_entry.media_type.empty()) {
7803 has_invalid_entry = true;
7804 return;
7805 }
7806
7807 // Check for basic media type format (should contain '/' or be '*')
7808 if (accept_entry.media_type != "*" &&
7809 accept_entry.media_type.find('/') == std::string::npos) {
7810 has_invalid_entry = true;
7811 return;
7812 }
7813
7814 entries.push_back(std::move(accept_entry));
7815 });
7816
7817 // Return false if any invalid entry was found
7818 if (has_invalid_entry) { return false; }
7819
7820 // Sort by quality (descending), then by original order (ascending)
7821 std::sort(entries.begin(), entries.end(),
7822 [](const AcceptEntry &a, const AcceptEntry &b) {
7823 if (a.quality != b.quality) {
7824 return a.quality > b.quality; // Higher quality first
7825 }
7826 return a.order < b.order; // Earlier order first for same quality
7827 });
7828
7829 // Extract sorted media types
7830 content_types.reserve(entries.size());
7831 for (auto &entry : entries) {
7832 content_types.push_back(std::move(entry.media_type));
7833 }
7834
7835 return true;
7836}
7837
7839public:
7840 FormDataParser() = default;
7841
7842 void set_boundary(std::string &&boundary) {
7843 boundary_ = std::move(boundary);
7844 dash_boundary_crlf_ = dash_ + boundary_ + crlf_;
7845 crlf_dash_boundary_ = crlf_ + dash_ + boundary_;
7846 }
7847
7848 bool is_valid() const { return is_valid_; }
7849
7850 bool parse(const char *buf, size_t n, const FormDataHeader &header_callback,
7851 const ContentReceiver &content_callback) {
7852
7853 buf_append(buf, n);
7854
7855 while (buf_size() > 0) {
7856 switch (state_) {
7857 case 0: { // Initial boundary
7858 auto pos = buf_find(dash_boundary_crlf_);
7859 if (pos == buf_size()) { return true; }
7860 buf_erase(pos + dash_boundary_crlf_.size());
7861 state_ = 1;
7862 break;
7863 }
7864 case 1: { // New entry
7865 clear_file_info();
7866 state_ = 2;
7867 break;
7868 }
7869 case 2: { // Headers
7870 auto pos = buf_find(crlf_);
7871 if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
7872 while (pos < buf_size()) {
7873 // Empty line
7874 if (pos == 0) {
7875 if (!header_callback(file_)) {
7876 is_valid_ = false;
7877 return false;
7878 }
7879 buf_erase(crlf_.size());
7880 state_ = 3;
7881 break;
7882 }
7883
7884 const auto header = buf_head(pos);
7885
7886 if (!parse_header(header.data(), header.data() + header.size(),
7887 [&](const std::string &, const std::string &) {})) {
7888 is_valid_ = false;
7889 return false;
7890 }
7891
7892 // Parse and emplace space trimmed headers into a map
7893 if (!parse_header(
7894 header.data(), header.data() + header.size(),
7895 [&](const std::string &key, const std::string &val) {
7896 file_.headers.emplace(key, val);
7897 })) {
7898 is_valid_ = false;
7899 return false;
7900 }
7901
7902 constexpr const char header_content_type[] = "Content-Type:";
7903
7904 if (start_with_case_ignore(header, header_content_type)) {
7905 file_.content_type =
7906 trim_copy(header.substr(str_len(header_content_type)));
7907 } else {
7908 std::string disposition_params;
7909 if (parse_content_disposition(header, disposition_params)) {
7910 Params params;
7911 parse_disposition_params(disposition_params, params);
7912
7913 auto it = params.find("name");
7914 if (it != params.end()) {
7915 file_.name = it->second;
7916 } else {
7917 is_valid_ = false;
7918 return false;
7919 }
7920
7921 it = params.find("filename");
7922 if (it != params.end()) { file_.filename = it->second; }
7923
7924 it = params.find("filename*");
7925 if (it != params.end()) {
7926 // RFC 5987: only UTF-8 encoding is allowed
7927 const auto &val = it->second;
7928 constexpr const char utf8_prefix[] = "UTF-8''";
7929 constexpr size_t prefix_len = str_len(utf8_prefix);
7930 if (val.size() > prefix_len &&
7931 start_with_case_ignore(val, utf8_prefix)) {
7932 file_.filename = decode_path_component(
7933 val.substr(prefix_len)); // override...
7934 } else {
7935 is_valid_ = false;
7936 return false;
7937 }
7938 }
7939 }
7940 }
7941 buf_erase(pos + crlf_.size());
7942 pos = buf_find(crlf_);
7943 }
7944 if (state_ != 3) { return true; }
7945 break;
7946 }
7947 case 3: { // Body
7948 if (crlf_dash_boundary_.size() > buf_size()) { return true; }
7949 auto pos = buf_find(crlf_dash_boundary_);
7950 if (pos < buf_size()) {
7951 if (!content_callback(buf_data(), pos)) {
7952 is_valid_ = false;
7953 return false;
7954 }
7955 buf_erase(pos + crlf_dash_boundary_.size());
7956 state_ = 4;
7957 } else {
7958 auto len = buf_size() - crlf_dash_boundary_.size();
7959 if (len > 0) {
7960 if (!content_callback(buf_data(), len)) {
7961 is_valid_ = false;
7962 return false;
7963 }
7964 buf_erase(len);
7965 }
7966 return true;
7967 }
7968 break;
7969 }
7970 case 4: { // Boundary
7971 if (crlf_.size() > buf_size()) { return true; }
7972 if (buf_start_with(crlf_)) {
7973 buf_erase(crlf_.size());
7974 state_ = 1;
7975 } else {
7976 if (dash_.size() > buf_size()) { return true; }
7977 if (buf_start_with(dash_)) {
7978 buf_erase(dash_.size());
7979 is_valid_ = true;
7980 buf_erase(buf_size()); // Remove epilogue
7981 } else {
7982 return true;
7983 }
7984 }
7985 break;
7986 }
7987 }
7988 }
7989
7990 return true;
7991 }
7992
7993private:
7994 void clear_file_info() {
7995 file_.name.clear();
7996 file_.filename.clear();
7997 file_.content_type.clear();
7998 file_.headers.clear();
7999 }
8000
8001 bool start_with_case_ignore(const std::string &a, const char *b,
8002 size_t offset = 0) const {
8003 const auto b_len = strlen(b);
8004 if (a.size() < offset + b_len) { return false; }
8005 for (size_t i = 0; i < b_len; i++) {
8006 if (case_ignore::to_lower(a[offset + i]) != case_ignore::to_lower(b[i])) {
8007 return false;
8008 }
8009 }
8010 return true;
8011 }
8012
8013 // Parses "Content-Disposition: form-data; <params>" without std::regex.
8014 // Returns true if header matches, with the params portion in `params_out`.
8015 bool parse_content_disposition(const std::string &header,
8016 std::string &params_out) const {
8017 constexpr const char prefix[] = "Content-Disposition:";
8018 constexpr size_t prefix_len = str_len(prefix);
8019
8020 if (!start_with_case_ignore(header, prefix)) { return false; }
8021
8022 // Skip whitespace after "Content-Disposition:"
8023 auto pos = prefix_len;
8024 while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) {
8025 pos++;
8026 }
8027
8028 // Match "form-data;" (case-insensitive)
8029 constexpr const char form_data[] = "form-data;";
8030 constexpr size_t form_data_len = str_len(form_data);
8031 if (!start_with_case_ignore(header, form_data, pos)) { return false; }
8032 pos += form_data_len;
8033
8034 // Skip whitespace after "form-data;"
8035 while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) {
8036 pos++;
8037 }
8038
8039 params_out = header.substr(pos);
8040 return true;
8041 }
8042
8043 const std::string dash_ = "--";
8044 const std::string crlf_ = "\r\n";
8045 std::string boundary_;
8046 std::string dash_boundary_crlf_;
8047 std::string crlf_dash_boundary_;
8048
8049 size_t state_ = 0;
8050 bool is_valid_ = false;
8051 FormData file_;
8052
8053 // Buffer
8054 bool start_with(const std::string &a, size_t spos, size_t epos,
8055 const std::string &b) const {
8056 if (epos - spos < b.size()) { return false; }
8057 for (size_t i = 0; i < b.size(); i++) {
8058 if (a[i + spos] != b[i]) { return false; }
8059 }
8060 return true;
8061 }
8062
8063 size_t buf_size() const { return buf_epos_ - buf_spos_; }
8064
8065 const char *buf_data() const { return &buf_[buf_spos_]; }
8066
8067 std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); }
8068
8069 bool buf_start_with(const std::string &s) const {
8070 return start_with(buf_, buf_spos_, buf_epos_, s);
8071 }
8072
8073 size_t buf_find(const std::string &s) const {
8074 auto c = s.front();
8075
8076 size_t off = buf_spos_;
8077 while (off < buf_epos_) {
8078 auto pos = off;
8079 while (true) {
8080 if (pos == buf_epos_) { return buf_size(); }
8081 if (buf_[pos] == c) { break; }
8082 pos++;
8083 }
8084
8085 auto remaining_size = buf_epos_ - pos;
8086 if (s.size() > remaining_size) { return buf_size(); }
8087
8088 if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; }
8089
8090 off = pos + 1;
8091 }
8092
8093 return buf_size();
8094 }
8095
8096 void buf_append(const char *data, size_t n) {
8097 auto remaining_size = buf_size();
8098 if (remaining_size > 0 && buf_spos_ > 0) {
8099 for (size_t i = 0; i < remaining_size; i++) {
8100 buf_[i] = buf_[buf_spos_ + i];
8101 }
8102 }
8103 buf_spos_ = 0;
8104 buf_epos_ = remaining_size;
8105
8106 if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); }
8107
8108 for (size_t i = 0; i < n; i++) {
8109 buf_[buf_epos_ + i] = data[i];
8110 }
8111 buf_epos_ += n;
8112 }
8113
8114 void buf_erase(size_t size) { buf_spos_ += size; }
8115
8116 std::string buf_;
8117 size_t buf_spos_ = 0;
8118 size_t buf_epos_ = 0;
8119};
8120
8121inline std::string random_string(size_t length) {
8122 constexpr const char data[] =
8123 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
8124
8125 thread_local auto engine([]() {
8126 // std::random_device might actually be deterministic on some
8127 // platforms, but due to lack of support in the c++ standard library,
8128 // doing better requires either some ugly hacks or breaking portability.
8129 std::random_device seed_gen;
8130 // Request 128 bits of entropy for initialization
8131 std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
8132 return std::mt19937(seed_sequence);
8133 }());
8134
8135 std::string result;
8136 for (size_t i = 0; i < length; i++) {
8137 result += data[engine() % (sizeof(data) - 1)];
8138 }
8139 return result;
8140}
8141
8142inline std::string make_multipart_data_boundary() {
8143 return "--cpp-httplib-multipart-data-" + detail::random_string(16);
8144}
8145
8146inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
8147 auto valid = true;
8148 for (size_t i = 0; i < boundary.size(); i++) {
8149 auto c = boundary[i];
8150 if (!std::isalnum(c) && c != '-' && c != '_') {
8151 valid = false;
8152 break;
8153 }
8154 }
8155 return valid;
8156}
8157
8158template <typename T>
8159inline std::string
8161 const std::string &boundary) {
8162 std::string body = "--" + boundary + "\r\n";
8163 body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
8164 if (!item.filename.empty()) {
8165 body += "; filename=\"" + item.filename + "\"";
8166 }
8167 body += "\r\n";
8168 if (!item.content_type.empty()) {
8169 body += "Content-Type: " + item.content_type + "\r\n";
8170 }
8171 body += "\r\n";
8172
8173 return body;
8174}
8175
8176inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
8177
8178inline std::string
8179serialize_multipart_formdata_finish(const std::string &boundary) {
8180 return "--" + boundary + "--\r\n";
8181}
8182
8183inline std::string
8185 return "multipart/form-data; boundary=" + boundary;
8186}
8187
8188inline std::string
8190 const std::string &boundary, bool finish = true) {
8191 std::string body;
8192
8193 for (const auto &item : items) {
8194 body += serialize_multipart_formdata_item_begin(item, boundary);
8195 body += item.content + serialize_multipart_formdata_item_end();
8196 }
8197
8198 if (finish) { body += serialize_multipart_formdata_finish(boundary); }
8199
8200 return body;
8201}
8202
8204 const std::string &boundary) {
8205 size_t total = 0;
8206 for (const auto &item : items) {
8207 total += serialize_multipart_formdata_item_begin(item, boundary).size();
8208 total += item.content.size();
8209 total += serialize_multipart_formdata_item_end().size();
8210 }
8211 total += serialize_multipart_formdata_finish(boundary).size();
8212 return total;
8213}
8214
8216 const char *data;
8217 size_t size;
8218};
8219
8220// NOTE: items must outlive the returned ContentProvider
8221// (safe for synchronous use inside Post/Put/Patch)
8222inline ContentProvider
8224 const std::string &boundary) {
8225 // Own the per-item header strings and the finish string
8226 std::vector<std::string> owned;
8227 owned.reserve(items.size() + 1);
8228 for (const auto &item : items)
8229 owned.push_back(serialize_multipart_formdata_item_begin(item, boundary));
8230 owned.push_back(serialize_multipart_formdata_finish(boundary));
8231
8232 // Flat segment list: [header, content, "\r\n"] * N + [finish]
8233 std::vector<MultipartSegment> segs;
8234 segs.reserve(items.size() * 3 + 1);
8235 static const char crlf[] = "\r\n";
8236 for (size_t i = 0; i < items.size(); i++) {
8237 segs.push_back({owned[i].data(), owned[i].size()});
8238 segs.push_back({items[i].content.data(), items[i].content.size()});
8239 segs.push_back({crlf, 2});
8240 }
8241 segs.push_back({owned.back().data(), owned.back().size()});
8242
8243 struct MultipartState {
8244 std::vector<std::string> owned;
8245 std::vector<MultipartSegment> segs;
8246 };
8247 auto state = std::make_shared<MultipartState>();
8248 state->owned = std::move(owned);
8249 // `segs` holds raw pointers into owned strings; std::string move preserves
8250 // the data pointer, so these pointers remain valid after the move above.
8251 state->segs = std::move(segs);
8252
8253 return [state](size_t offset, size_t length, DataSink &sink) -> bool {
8254 size_t pos = 0;
8255 for (const auto &seg : state->segs) {
8256 // Loop invariant: pos <= offset (proven by advancing pos only when
8257 // offset - pos >= seg.size, i.e., the segment doesn't contain offset)
8258 if (seg.size > 0 && offset - pos < seg.size) {
8259 size_t seg_offset = offset - pos;
8260 size_t available = seg.size - seg_offset;
8261 size_t to_write = (std::min)(available, length);
8262 return sink.write(seg.data + seg_offset, to_write);
8263 }
8264 pos += seg.size;
8265 }
8266 return true; // past end (shouldn't be reached when content_length is exact)
8267 };
8268}
8269
8270inline void coalesce_ranges(Ranges &ranges, size_t content_length) {
8271 if (ranges.size() <= 1) return;
8272
8273 // Sort ranges by start position
8274 std::sort(ranges.begin(), ranges.end(),
8275 [](const Range &a, const Range &b) { return a.first < b.first; });
8276
8277 Ranges coalesced;
8278 coalesced.reserve(ranges.size());
8279
8280 for (auto &r : ranges) {
8281 auto first_pos = r.first;
8282 auto last_pos = r.second;
8283
8284 // Handle special cases like in range_error
8285 if (first_pos == -1 && last_pos == -1) {
8286 first_pos = 0;
8287 last_pos = static_cast<ssize_t>(content_length);
8288 }
8289
8290 if (first_pos == -1) {
8291 first_pos = static_cast<ssize_t>(content_length) - last_pos;
8292 last_pos = static_cast<ssize_t>(content_length) - 1;
8293 }
8294
8295 if (last_pos == -1 || last_pos >= static_cast<ssize_t>(content_length)) {
8296 last_pos = static_cast<ssize_t>(content_length) - 1;
8297 }
8298
8299 // Skip invalid ranges
8300 if (!(0 <= first_pos && first_pos <= last_pos &&
8301 last_pos < static_cast<ssize_t>(content_length))) {
8302 continue;
8303 }
8304
8305 // Coalesce with previous range if overlapping or adjacent (but not
8306 // identical)
8307 if (!coalesced.empty()) {
8308 auto &prev = coalesced.back();
8309 // Check if current range overlaps or is adjacent to previous range
8310 // but don't coalesce identical ranges (allow duplicates)
8311 if (first_pos <= prev.second + 1 &&
8312 !(first_pos == prev.first && last_pos == prev.second)) {
8313 // Extend the previous range
8314 prev.second = (std::max)(prev.second, last_pos);
8315 continue;
8316 }
8317 }
8318
8319 // Add new range
8320 coalesced.emplace_back(first_pos, last_pos);
8321 }
8322
8323 ranges = std::move(coalesced);
8324}
8325
8326inline bool range_error(Request &req, Response &res) {
8327 if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
8328 ssize_t content_len = static_cast<ssize_t>(
8329 res.content_length_ ? res.content_length_ : res.body.size());
8330
8331 std::vector<std::pair<ssize_t, ssize_t>> processed_ranges;
8332 size_t overwrapping_count = 0;
8333
8334 // NOTE: The following Range check is based on '14.2. Range' in RFC 9110
8335 // 'HTTP Semantics' to avoid potential denial-of-service attacks.
8336 // https://www.rfc-editor.org/rfc/rfc9110#section-14.2
8337
8338 // Too many ranges
8339 if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; }
8340
8341 for (auto &r : req.ranges) {
8342 auto &first_pos = r.first;
8343 auto &last_pos = r.second;
8344
8345 if (first_pos == -1 && last_pos == -1) {
8346 first_pos = 0;
8347 last_pos = content_len;
8348 }
8349
8350 if (first_pos == -1) {
8351 first_pos = content_len - last_pos;
8352 last_pos = content_len - 1;
8353 }
8354
8355 // NOTE: RFC-9110 '14.1.2. Byte Ranges':
8356 // A client can limit the number of bytes requested without knowing the
8357 // size of the selected representation. If the last-pos value is absent,
8358 // or if the value is greater than or equal to the current length of the
8359 // representation data, the byte range is interpreted as the remainder of
8360 // the representation (i.e., the server replaces the value of last-pos
8361 // with a value that is one less than the current length of the selected
8362 // representation).
8363 // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
8364 if (last_pos == -1 || last_pos >= content_len) {
8365 last_pos = content_len - 1;
8366 }
8367
8368 // Range must be within content length
8369 if (!(0 <= first_pos && first_pos <= last_pos &&
8370 last_pos <= content_len - 1)) {
8371 return true;
8372 }
8373
8374 // Request must not have more than two overlapping ranges
8375 for (const auto &processed_range : processed_ranges) {
8376 if (!(last_pos < processed_range.first ||
8377 first_pos > processed_range.second)) {
8378 overwrapping_count++;
8379 if (overwrapping_count > 2) { return true; }
8380 break; // Only count once per range
8381 }
8382 }
8383
8384 processed_ranges.emplace_back(first_pos, last_pos);
8385 }
8386
8387 // After validation, coalesce overlapping ranges as per RFC 9110
8388 coalesce_ranges(req.ranges, static_cast<size_t>(content_len));
8389 }
8390
8391 return false;
8392}
8393
8394inline std::pair<size_t, size_t>
8395get_range_offset_and_length(Range r, size_t content_length) {
8396 assert(r.first != -1 && r.second != -1);
8397 assert(0 <= r.first && r.first < static_cast<ssize_t>(content_length));
8398 assert(r.first <= r.second &&
8399 r.second < static_cast<ssize_t>(content_length));
8400 (void)(content_length);
8401 return std::make_pair(static_cast<size_t>(r.first),
8402 static_cast<size_t>(r.second - r.first) + 1);
8403}
8404
8406 const std::pair<size_t, size_t> &offset_and_length, size_t content_length) {
8407 auto st = offset_and_length.first;
8408 auto ed = st + offset_and_length.second - 1;
8409
8410 std::string field = "bytes ";
8411 field += std::to_string(st);
8412 field += '-';
8413 field += std::to_string(ed);
8414 field += '/';
8415 field += std::to_string(content_length);
8416 return field;
8417}
8418
8419template <typename SToken, typename CToken, typename Content>
8421 const std::string &boundary,
8422 const std::string &content_type,
8423 size_t content_length, SToken stoken,
8424 CToken ctoken, Content content) {
8425 for (size_t i = 0; i < req.ranges.size(); i++) {
8426 ctoken("--");
8427 stoken(boundary);
8428 ctoken("\r\n");
8429 if (!content_type.empty()) {
8430 ctoken("Content-Type: ");
8431 stoken(content_type);
8432 ctoken("\r\n");
8433 }
8434
8435 auto offset_and_length =
8436 get_range_offset_and_length(req.ranges[i], content_length);
8437
8438 ctoken("Content-Range: ");
8439 stoken(make_content_range_header_field(offset_and_length, content_length));
8440 ctoken("\r\n");
8441 ctoken("\r\n");
8442
8443 if (!content(offset_and_length.first, offset_and_length.second)) {
8444 return false;
8445 }
8446 ctoken("\r\n");
8447 }
8448
8449 ctoken("--");
8450 stoken(boundary);
8451 ctoken("--");
8452
8453 return true;
8454}
8455
8456inline void make_multipart_ranges_data(const Request &req, Response &res,
8457 const std::string &boundary,
8458 const std::string &content_type,
8459 size_t content_length,
8460 std::string &data) {
8462 req, boundary, content_type, content_length,
8463 [&](const std::string &token) { data += token; },
8464 [&](const std::string &token) { data += token; },
8465 [&](size_t offset, size_t length) {
8466 assert(offset + length <= content_length);
8467 data += res.body.substr(offset, length);
8468 return true;
8469 });
8470}
8471
8473 const std::string &boundary,
8474 const std::string &content_type,
8475 size_t content_length) {
8476 size_t data_length = 0;
8477
8479 req, boundary, content_type, content_length,
8480 [&](const std::string &token) { data_length += token.size(); },
8481 [&](const std::string &token) { data_length += token.size(); },
8482 [&](size_t /*offset*/, size_t length) {
8483 data_length += length;
8484 return true;
8485 });
8486
8487 return data_length;
8488}
8489
8490template <typename T>
8491inline bool
8493 const std::string &boundary,
8494 const std::string &content_type,
8495 size_t content_length, const T &is_shutting_down) {
8497 req, boundary, content_type, content_length,
8498 [&](const std::string &token) { strm.write(token); },
8499 [&](const std::string &token) { strm.write(token); },
8500 [&](size_t offset, size_t length) {
8501 return write_content(strm, res.content_provider_, offset, length,
8502 is_shutting_down);
8503 });
8504}
8505
8506inline bool expect_content(const Request &req) {
8507 if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
8508 req.method == "DELETE") {
8509 return true;
8510 }
8511 if (req.has_header("Content-Length") &&
8512 req.get_header_value_u64("Content-Length") > 0) {
8513 return true;
8514 }
8515 if (is_chunked_transfer_encoding(req.headers)) { return true; }
8516 return false;
8517}
8518
8519#ifdef _WIN32
8520class WSInit {
8521public:
8522 WSInit() {
8523 WSADATA wsaData;
8524 if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true;
8525 }
8526
8527 ~WSInit() {
8528 if (is_valid_) WSACleanup();
8529 }
8530
8531 bool is_valid_ = false;
8532};
8533
8534static WSInit wsinit_;
8535#endif
8536
8537inline bool parse_www_authenticate(const Response &res,
8538 std::map<std::string, std::string> &auth,
8539 bool is_proxy) {
8540 auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
8541 if (res.has_header(auth_key)) {
8542 thread_local auto re =
8543 std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
8544 auto s = res.get_header_value(auth_key);
8545 auto pos = s.find(' ');
8546 if (pos != std::string::npos) {
8547 auto type = s.substr(0, pos);
8548 if (type == "Basic") {
8549 return false;
8550 } else if (type == "Digest") {
8551 s = s.substr(pos + 1);
8552 auto beg = std::sregex_iterator(s.begin(), s.end(), re);
8553 for (auto i = beg; i != std::sregex_iterator(); ++i) {
8554 const auto &m = *i;
8555 auto key = s.substr(static_cast<size_t>(m.position(1)),
8556 static_cast<size_t>(m.length(1)));
8557 auto val = m.length(2) > 0
8558 ? s.substr(static_cast<size_t>(m.position(2)),
8559 static_cast<size_t>(m.length(2)))
8560 : s.substr(static_cast<size_t>(m.position(3)),
8561 static_cast<size_t>(m.length(3)));
8562 auth[std::move(key)] = std::move(val);
8563 }
8564 return true;
8565 }
8566 }
8567 }
8568 return false;
8569}
8570
8572public:
8574 ContentProviderWithoutLength &&content_provider)
8575 : content_provider_(std::move(content_provider)) {}
8576
8577 bool operator()(size_t offset, size_t, DataSink &sink) {
8578 return content_provider_(offset, sink);
8579 }
8580
8581private:
8582 ContentProviderWithoutLength content_provider_;
8583};
8584
8585// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
8586namespace fields {
8587
8588inline bool is_token_char(char c) {
8589 return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
8590 c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
8591 c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
8592}
8593
8594inline bool is_token(const std::string &s) {
8595 if (s.empty()) { return false; }
8596 for (auto c : s) {
8597 if (!is_token_char(c)) { return false; }
8598 }
8599 return true;
8600}
8601
8602inline bool is_field_name(const std::string &s) { return is_token(s); }
8603
8604inline bool is_vchar(char c) { return c >= 33 && c <= 126; }
8605
8606inline bool is_obs_text(char c) { return 128 <= static_cast<unsigned char>(c); }
8607
8608inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); }
8609
8610inline bool is_field_content(const std::string &s) {
8611 if (s.empty()) { return true; }
8612
8613 if (s.size() == 1) {
8614 return is_field_vchar(s[0]);
8615 } else if (s.size() == 2) {
8616 return is_field_vchar(s[0]) && is_field_vchar(s[1]);
8617 } else {
8618 size_t i = 0;
8619
8620 if (!is_field_vchar(s[i])) { return false; }
8621 i++;
8622
8623 while (i < s.size() - 1) {
8624 auto c = s[i++];
8625 if (c == ' ' || c == '\t' || is_field_vchar(c)) {
8626 } else {
8627 return false;
8628 }
8629 }
8630
8631 return is_field_vchar(s[i]);
8632 }
8633}
8634
8635inline bool is_field_value(const std::string &s) { return is_field_content(s); }
8636
8637} // namespace fields
8638
8639inline bool perform_websocket_handshake(Stream &strm, const std::string &host,
8640 int port, const std::string &path,
8641 const Headers &headers,
8642 std::string &selected_subprotocol) {
8643 // Validate path and host
8644 if (!fields::is_field_value(path) || !fields::is_field_value(host)) {
8645 return false;
8646 }
8647
8648 // Validate user-provided headers
8649 for (const auto &h : headers) {
8650 if (!fields::is_field_name(h.first) || !fields::is_field_value(h.second)) {
8651 return false;
8652 }
8653 }
8654
8655 // Generate random Sec-WebSocket-Key
8656 thread_local std::mt19937 rng(std::random_device{}());
8657 std::string key_bytes(16, '\0');
8658 for (size_t i = 0; i < 16; i += 4) {
8659 auto r = rng();
8660 std::memcpy(&key_bytes[i], &r, (std::min)(size_t(4), size_t(16 - i)));
8661 }
8662 auto client_key = base64_encode(key_bytes);
8663
8664 // Build upgrade request
8665 std::string req_str = "GET " + path + " HTTP/1.1\r\n";
8666 req_str += "Host: " + host + ":" + std::to_string(port) + "\r\n";
8667 req_str += "Upgrade: websocket\r\n";
8668 req_str += "Connection: Upgrade\r\n";
8669 req_str += "Sec-WebSocket-Key: " + client_key + "\r\n";
8670 req_str += "Sec-WebSocket-Version: 13\r\n";
8671 for (const auto &h : headers) {
8672 req_str += h.first + ": " + h.second + "\r\n";
8673 }
8674 req_str += "\r\n";
8675
8676 if (strm.write(req_str.data(), req_str.size()) < 0) { return false; }
8677
8678 // Verify 101 response and Sec-WebSocket-Accept header
8679 auto expected_accept = websocket_accept_key(client_key);
8680 return read_websocket_upgrade_response(strm, expected_accept,
8681 selected_subprotocol);
8682}
8683
8684} // namespace detail
8685
8686/*
8687 * Group 2: detail namespace - SSL common utilities
8688 */
8689
8690#ifdef CPPHTTPLIB_SSL_ENABLED
8691namespace detail {
8692
8693class SSLSocketStream final : public Stream {
8694public:
8695 SSLSocketStream(
8696 socket_t sock, tls::session_t session, time_t read_timeout_sec,
8697 time_t read_timeout_usec, time_t write_timeout_sec,
8698 time_t write_timeout_usec, time_t max_timeout_msec = 0,
8699 std::chrono::time_point<std::chrono::steady_clock> start_time =
8700 (std::chrono::steady_clock::time_point::min)());
8701 ~SSLSocketStream() override;
8702
8703 bool is_readable() const override;
8704 bool wait_readable() const override;
8705 bool wait_writable() const override;
8706 bool is_peer_alive() const override;
8707 ssize_t read(char *ptr, size_t size) override;
8708 ssize_t write(const char *ptr, size_t size) override;
8709 void get_remote_ip_and_port(std::string &ip, int &port) const override;
8710 void get_local_ip_and_port(std::string &ip, int &port) const override;
8711 socket_t socket() const override;
8712 time_t duration() const override;
8713 void set_read_timeout(time_t sec, time_t usec = 0) override;
8714
8715private:
8716 socket_t sock_;
8717 tls::session_t session_;
8718 time_t read_timeout_sec_;
8719 time_t read_timeout_usec_;
8720 time_t write_timeout_sec_;
8721 time_t write_timeout_usec_;
8722 time_t max_timeout_msec_;
8723 const std::chrono::time_point<std::chrono::steady_clock> start_time_;
8724};
8725
8726#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8727inline std::string message_digest(const std::string &s, const EVP_MD *algo) {
8728 auto context = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
8729 EVP_MD_CTX_new(), EVP_MD_CTX_free);
8730
8731 unsigned int hash_length = 0;
8732 unsigned char hash[EVP_MAX_MD_SIZE];
8733
8734 EVP_DigestInit_ex(context.get(), algo, nullptr);
8735 EVP_DigestUpdate(context.get(), s.c_str(), s.size());
8736 EVP_DigestFinal_ex(context.get(), hash, &hash_length);
8737
8738 std::stringstream ss;
8739 for (auto i = 0u; i < hash_length; ++i) {
8740 ss << std::hex << std::setw(2) << std::setfill('0')
8741 << static_cast<unsigned int>(hash[i]);
8742 }
8743
8744 return ss.str();
8745}
8746
8747inline std::string MD5(const std::string &s) {
8748 return message_digest(s, EVP_md5());
8749}
8750
8751inline std::string SHA_256(const std::string &s) {
8752 return message_digest(s, EVP_sha256());
8753}
8754
8755inline std::string SHA_512(const std::string &s) {
8756 return message_digest(s, EVP_sha512());
8757}
8758#elif defined(CPPHTTPLIB_MBEDTLS_SUPPORT)
8759namespace {
8760template <size_t N>
8761inline std::string hash_to_hex(const unsigned char (&hash)[N]) {
8762 std::stringstream ss;
8763 for (size_t i = 0; i < N; ++i) {
8764 ss << std::hex << std::setw(2) << std::setfill('0')
8765 << static_cast<unsigned int>(hash[i]);
8766 }
8767 return ss.str();
8768}
8769} // namespace
8770
8771inline std::string MD5(const std::string &s) {
8772 unsigned char hash[16];
8773#ifdef CPPHTTPLIB_MBEDTLS_V3
8774 mbedtls_md5(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
8775 hash);
8776#else
8777 mbedtls_md5_ret(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
8778 hash);
8779#endif
8780 return hash_to_hex(hash);
8781}
8782
8783inline std::string SHA_256(const std::string &s) {
8784 unsigned char hash[32];
8785#ifdef CPPHTTPLIB_MBEDTLS_V3
8786 mbedtls_sha256(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
8787 hash, 0);
8788#else
8789 mbedtls_sha256_ret(reinterpret_cast<const unsigned char *>(s.c_str()),
8790 s.size(), hash, 0);
8791#endif
8792 return hash_to_hex(hash);
8793}
8794
8795inline std::string SHA_512(const std::string &s) {
8796 unsigned char hash[64];
8797#ifdef CPPHTTPLIB_MBEDTLS_V3
8798 mbedtls_sha512(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
8799 hash, 0);
8800#else
8801 mbedtls_sha512_ret(reinterpret_cast<const unsigned char *>(s.c_str()),
8802 s.size(), hash, 0);
8803#endif
8804 return hash_to_hex(hash);
8805}
8806#elif defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
8807namespace {
8808template <size_t N>
8809inline std::string hash_to_hex(const unsigned char (&hash)[N]) {
8810 std::stringstream ss;
8811 for (size_t i = 0; i < N; ++i) {
8812 ss << std::hex << std::setw(2) << std::setfill('0')
8813 << static_cast<unsigned int>(hash[i]);
8814 }
8815 return ss.str();
8816}
8817} // namespace
8818
8819inline std::string MD5(const std::string &s) {
8820 unsigned char hash[WC_MD5_DIGEST_SIZE];
8821 wc_Md5Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
8822 static_cast<word32>(s.size()), hash);
8823 return hash_to_hex(hash);
8824}
8825
8826inline std::string SHA_256(const std::string &s) {
8827 unsigned char hash[WC_SHA256_DIGEST_SIZE];
8828 wc_Sha256Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
8829 static_cast<word32>(s.size()), hash);
8830 return hash_to_hex(hash);
8831}
8832
8833inline std::string SHA_512(const std::string &s) {
8834 unsigned char hash[WC_SHA512_DIGEST_SIZE];
8835 wc_Sha512Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
8836 static_cast<word32>(s.size()), hash);
8837 return hash_to_hex(hash);
8838}
8839#endif
8840
8841inline bool is_ip_address(const std::string &host) {
8842 struct in_addr addr4;
8843 struct in6_addr addr6;
8844 return inet_pton(AF_INET, host.c_str(), &addr4) == 1 ||
8845 inet_pton(AF_INET6, host.c_str(), &addr6) == 1;
8846}
8847
8848template <typename T>
8849inline bool process_server_socket_ssl(
8850 const std::atomic<socket_t> &svr_sock, tls::session_t session,
8851 socket_t sock, size_t keep_alive_max_count, time_t keep_alive_timeout_sec,
8852 time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
8853 time_t write_timeout_usec, T callback) {
8855 svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
8856 [&](bool close_connection, bool &connection_closed) {
8857 SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
8858 write_timeout_sec, write_timeout_usec);
8859 return callback(strm, close_connection, connection_closed);
8860 });
8861}
8862
8863template <typename T>
8864inline bool process_client_socket_ssl(
8865 tls::session_t session, socket_t sock, time_t read_timeout_sec,
8866 time_t read_timeout_usec, time_t write_timeout_sec,
8867 time_t write_timeout_usec, time_t max_timeout_msec,
8868 std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
8869 SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
8870 write_timeout_sec, write_timeout_usec, max_timeout_msec,
8871 start_time);
8872 return callback(strm);
8873}
8874
8875inline std::pair<std::string, std::string> make_digest_authentication_header(
8876 const Request &req, const std::map<std::string, std::string> &auth,
8877 size_t cnonce_count, const std::string &cnonce, const std::string &username,
8878 const std::string &password, bool is_proxy = false) {
8879 std::string nc;
8880 {
8881 std::stringstream ss;
8882 ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
8883 nc = ss.str();
8884 }
8885
8886 std::string qop;
8887 if (auth.find("qop") != auth.end()) {
8888 qop = auth.at("qop");
8889 if (qop.find("auth-int") != std::string::npos) {
8890 qop = "auth-int";
8891 } else if (qop.find("auth") != std::string::npos) {
8892 qop = "auth";
8893 } else {
8894 qop.clear();
8895 }
8896 }
8897
8898 std::string algo = "MD5";
8899 if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
8900
8901 std::string response;
8902 {
8903 auto H = algo == "SHA-256" ? detail::SHA_256
8904 : algo == "SHA-512" ? detail::SHA_512
8905 : detail::MD5;
8906
8907 auto A1 = username + ":" + auth.at("realm") + ":" + password;
8908
8909 auto A2 = req.method + ":" + req.path;
8910 if (qop == "auth-int") { A2 += ":" + H(req.body); }
8911
8912 if (qop.empty()) {
8913 response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
8914 } else {
8915 response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
8916 ":" + qop + ":" + H(A2));
8917 }
8918 }
8919
8920 auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
8921
8922 auto field = "Digest username=\"" + username + "\", realm=\"" +
8923 auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
8924 "\", uri=\"" + req.path + "\", algorithm=" + algo +
8925 (qop.empty() ? ", response=\""
8926 : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
8927 cnonce + "\", response=\"") +
8928 response + "\"" +
8929 (opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
8930
8931 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
8932 return std::make_pair(key, field);
8933}
8934
8935inline bool match_hostname(const std::string &pattern,
8936 const std::string &hostname) {
8937 // Exact match (case-insensitive)
8938 if (detail::case_ignore::equal(hostname, pattern)) { return true; }
8939
8940 // Split both pattern and hostname into components by '.'
8941 std::vector<std::string> pattern_components;
8942 if (!pattern.empty()) {
8943 split(pattern.data(), pattern.data() + pattern.size(), '.',
8944 [&](const char *b, const char *e) {
8945 pattern_components.emplace_back(b, e);
8946 });
8947 }
8948
8949 std::vector<std::string> host_components;
8950 if (!hostname.empty()) {
8951 split(hostname.data(), hostname.data() + hostname.size(), '.',
8952 [&](const char *b, const char *e) {
8953 host_components.emplace_back(b, e);
8954 });
8955 }
8956
8957 // Component count must match
8958 if (host_components.size() != pattern_components.size()) { return false; }
8959
8960 // Compare each component with wildcard support
8961 // Supports: "*" (full wildcard), "prefix*" (partial wildcard)
8962 // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
8963 auto itr = pattern_components.begin();
8964 for (const auto &h : host_components) {
8965 auto &p = *itr;
8966 if (!detail::case_ignore::equal(p, h) && p != "*") {
8967 bool partial_match = false;
8968 if (!p.empty() && p[p.size() - 1] == '*') {
8969 const auto prefix_length = p.size() - 1;
8970 if (prefix_length == 0) {
8971 partial_match = true;
8972 } else if (h.size() >= prefix_length) {
8973 partial_match =
8974 std::equal(p.begin(),
8975 p.begin() + static_cast<std::string::difference_type>(
8976 prefix_length),
8977 h.begin(), [](const char ca, const char cb) {
8978 return detail::case_ignore::to_lower(ca) ==
8979 detail::case_ignore::to_lower(cb);
8980 });
8981 }
8982 }
8983 if (!partial_match) { return false; }
8984 }
8985 ++itr;
8986 }
8987
8988 return true;
8989}
8990
8991#ifdef _WIN32
8992// Verify certificate using Windows CertGetCertificateChain API.
8993// This provides real-time certificate validation with Windows Update
8994// integration, independent of the TLS backend (OpenSSL or MbedTLS).
8995inline bool
8996verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
8997 const std::string &hostname,
8998 bool verify_hostname, uint64_t &out_error) {
8999 if (der_cert.empty()) { return false; }
9000
9001 out_error = 0;
9002
9003 // Create Windows certificate context from DER data
9004 auto cert_context = CertCreateCertificateContext(
9005 X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_cert.data(),
9006 static_cast<DWORD>(der_cert.size()));
9007
9008 if (!cert_context) {
9009 out_error = GetLastError();
9010 return false;
9011 }
9012
9013 auto cert_guard =
9014 scope_exit([&] { CertFreeCertificateContext(cert_context); });
9015
9016 // Setup chain parameters
9017 CERT_CHAIN_PARA chain_para = {};
9018 chain_para.cbSize = sizeof(chain_para);
9019
9020 // Build certificate chain with revocation checking
9021 PCCERT_CHAIN_CONTEXT chain_context = nullptr;
9022 auto chain_result = CertGetCertificateChain(
9023 nullptr, cert_context, nullptr, cert_context->hCertStore, &chain_para,
9024 CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_END_CERT |
9025 CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT,
9026 nullptr, &chain_context);
9027
9028 if (!chain_result || !chain_context) {
9029 out_error = GetLastError();
9030 return false;
9031 }
9032
9033 auto chain_guard =
9034 scope_exit([&] { CertFreeCertificateChain(chain_context); });
9035
9036 // Check if chain has errors
9037 if (chain_context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) {
9038 out_error = chain_context->TrustStatus.dwErrorStatus;
9039 return false;
9040 }
9041
9042 // Verify SSL policy
9043 SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para = {};
9044 extra_policy_para.cbSize = sizeof(extra_policy_para);
9045#ifdef AUTHTYPE_SERVER
9046 extra_policy_para.dwAuthType = AUTHTYPE_SERVER;
9047#endif
9048
9049 std::wstring whost;
9050 if (verify_hostname) {
9051 whost = u8string_to_wstring(hostname.c_str());
9052 extra_policy_para.pwszServerName = const_cast<wchar_t *>(whost.c_str());
9053 }
9054
9055 CERT_CHAIN_POLICY_PARA policy_para = {};
9056 policy_para.cbSize = sizeof(policy_para);
9057#ifdef CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS
9058 policy_para.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
9059#else
9060 policy_para.dwFlags = 0;
9061#endif
9062 policy_para.pvExtraPolicyPara = &extra_policy_para;
9063
9064 CERT_CHAIN_POLICY_STATUS policy_status = {};
9065 policy_status.cbSize = sizeof(policy_status);
9066
9067 if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chain_context,
9068 &policy_para, &policy_status)) {
9069 out_error = GetLastError();
9070 return false;
9071 }
9072
9073 if (policy_status.dwError != 0) {
9074 out_error = policy_status.dwError;
9075 return false;
9076 }
9077
9078 return true;
9079}
9080#endif // _WIN32
9081
9082inline bool setup_client_tls_session(const std::string &host, tls::ctx_t &ctx,
9083 tls::session_t &session, socket_t sock,
9084 bool server_certificate_verification,
9085 const std::string &ca_cert_file_path,
9086 tls::ca_store_t ca_cert_store,
9087 time_t timeout_sec, time_t timeout_usec) {
9088 using namespace tls;
9089
9090 ctx = create_client_context();
9091 if (!ctx) { return false; }
9092
9093 if (server_certificate_verification) {
9094 if (!ca_cert_file_path.empty()) {
9095 load_ca_file(ctx, ca_cert_file_path.c_str());
9096 }
9097 if (ca_cert_store) { set_ca_store(ctx, ca_cert_store); }
9098 load_system_certs(ctx);
9099 }
9100
9101 bool is_ip = is_ip_address(host);
9102
9103#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
9104 if (is_ip && server_certificate_verification) {
9105 set_verify_client(ctx, false);
9106 } else {
9107 set_verify_client(ctx, server_certificate_verification);
9108 }
9109#endif
9110
9111 session = create_session(ctx, sock);
9112 if (!session) { return false; }
9113
9114 // RFC 6066: SNI must not be set for IP addresses
9115 if (!is_ip) { set_sni(session, host.c_str()); }
9116 if (server_certificate_verification) { set_hostname(session, host.c_str()); }
9117
9118 if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec, nullptr)) {
9119 return false;
9120 }
9121
9122 if (server_certificate_verification) {
9123 if (get_verify_result(session) != 0) { return false; }
9124 }
9125
9126 return true;
9127}
9128
9129} // namespace detail
9130#endif // CPPHTTPLIB_SSL_ENABLED
9131
9132/*
9133 * Group 3: httplib namespace - Non-SSL public API implementations
9134 */
9135
9137 detail::set_socket_opt(sock, SOL_SOCKET,
9138#ifdef SO_REUSEPORT
9139 SO_REUSEPORT,
9140#else
9141 SO_REUSEADDR,
9142#endif
9143 1);
9144}
9145
9146inline std::string get_bearer_token_auth(const Request &req) {
9147 if (req.has_header("Authorization")) {
9148 constexpr auto bearer_header_prefix_len = detail::str_len("Bearer ");
9149 return req.get_header_value("Authorization")
9150 .substr(bearer_header_prefix_len);
9151 }
9152 return "";
9153}
9154
9155inline const char *status_message(int status) {
9156 switch (status) {
9157 case StatusCode::Continue_100: return "Continue";
9158 case StatusCode::SwitchingProtocol_101: return "Switching Protocol";
9159 case StatusCode::Processing_102: return "Processing";
9160 case StatusCode::EarlyHints_103: return "Early Hints";
9161 case StatusCode::OK_200: return "OK";
9162 case StatusCode::Created_201: return "Created";
9163 case StatusCode::Accepted_202: return "Accepted";
9165 return "Non-Authoritative Information";
9166 case StatusCode::NoContent_204: return "No Content";
9167 case StatusCode::ResetContent_205: return "Reset Content";
9168 case StatusCode::PartialContent_206: return "Partial Content";
9169 case StatusCode::MultiStatus_207: return "Multi-Status";
9170 case StatusCode::AlreadyReported_208: return "Already Reported";
9171 case StatusCode::IMUsed_226: return "IM Used";
9172 case StatusCode::MultipleChoices_300: return "Multiple Choices";
9173 case StatusCode::MovedPermanently_301: return "Moved Permanently";
9174 case StatusCode::Found_302: return "Found";
9175 case StatusCode::SeeOther_303: return "See Other";
9176 case StatusCode::NotModified_304: return "Not Modified";
9177 case StatusCode::UseProxy_305: return "Use Proxy";
9178 case StatusCode::unused_306: return "unused";
9179 case StatusCode::TemporaryRedirect_307: return "Temporary Redirect";
9180 case StatusCode::PermanentRedirect_308: return "Permanent Redirect";
9181 case StatusCode::BadRequest_400: return "Bad Request";
9182 case StatusCode::Unauthorized_401: return "Unauthorized";
9183 case StatusCode::PaymentRequired_402: return "Payment Required";
9184 case StatusCode::Forbidden_403: return "Forbidden";
9185 case StatusCode::NotFound_404: return "Not Found";
9186 case StatusCode::MethodNotAllowed_405: return "Method Not Allowed";
9187 case StatusCode::NotAcceptable_406: return "Not Acceptable";
9189 return "Proxy Authentication Required";
9190 case StatusCode::RequestTimeout_408: return "Request Timeout";
9191 case StatusCode::Conflict_409: return "Conflict";
9192 case StatusCode::Gone_410: return "Gone";
9193 case StatusCode::LengthRequired_411: return "Length Required";
9194 case StatusCode::PreconditionFailed_412: return "Precondition Failed";
9195 case StatusCode::PayloadTooLarge_413: return "Payload Too Large";
9196 case StatusCode::UriTooLong_414: return "URI Too Long";
9197 case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type";
9198 case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable";
9199 case StatusCode::ExpectationFailed_417: return "Expectation Failed";
9200 case StatusCode::ImATeapot_418: return "I'm a teapot";
9201 case StatusCode::MisdirectedRequest_421: return "Misdirected Request";
9202 case StatusCode::UnprocessableContent_422: return "Unprocessable Content";
9203 case StatusCode::Locked_423: return "Locked";
9204 case StatusCode::FailedDependency_424: return "Failed Dependency";
9205 case StatusCode::TooEarly_425: return "Too Early";
9206 case StatusCode::UpgradeRequired_426: return "Upgrade Required";
9207 case StatusCode::PreconditionRequired_428: return "Precondition Required";
9208 case StatusCode::TooManyRequests_429: return "Too Many Requests";
9210 return "Request Header Fields Too Large";
9212 return "Unavailable For Legal Reasons";
9213 case StatusCode::NotImplemented_501: return "Not Implemented";
9214 case StatusCode::BadGateway_502: return "Bad Gateway";
9215 case StatusCode::ServiceUnavailable_503: return "Service Unavailable";
9216 case StatusCode::GatewayTimeout_504: return "Gateway Timeout";
9218 return "HTTP Version Not Supported";
9219 case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates";
9220 case StatusCode::InsufficientStorage_507: return "Insufficient Storage";
9221 case StatusCode::LoopDetected_508: return "Loop Detected";
9222 case StatusCode::NotExtended_510: return "Not Extended";
9224 return "Network Authentication Required";
9225
9226 default:
9227 case StatusCode::InternalServerError_500: return "Internal Server Error";
9228 }
9229}
9230
9231inline std::string to_string(const Error error) {
9232 switch (error) {
9233 case Error::Success: return "Success (no error)";
9234 case Error::Unknown: return "Unknown";
9235 case Error::Connection: return "Could not establish connection";
9236 case Error::BindIPAddress: return "Failed to bind IP address";
9237 case Error::Read: return "Failed to read connection";
9238 case Error::Write: return "Failed to write connection";
9239 case Error::ExceedRedirectCount: return "Maximum redirect count exceeded";
9240 case Error::Canceled: return "Connection handling canceled";
9241 case Error::SSLConnection: return "SSL connection failed";
9242 case Error::SSLLoadingCerts: return "SSL certificate loading failed";
9243 case Error::SSLServerVerification: return "SSL server verification failed";
9245 return "SSL server hostname verification failed";
9247 return "Unsupported HTTP multipart boundary characters";
9248 case Error::Compression: return "Compression failed";
9249 case Error::ConnectionTimeout: return "Connection timed out";
9250 case Error::ProxyConnection: return "Proxy connection failed";
9251 case Error::ConnectionClosed: return "Connection closed by server";
9252 case Error::Timeout: return "Read timeout";
9253 case Error::ResourceExhaustion: return "Resource exhaustion";
9254 case Error::TooManyFormDataFiles: return "Too many form data files";
9255 case Error::ExceedMaxPayloadSize: return "Exceeded maximum payload size";
9256 case Error::ExceedUriMaxLength: return "Exceeded maximum URI length";
9258 return "Exceeded maximum socket descriptor count";
9259 case Error::InvalidRequestLine: return "Invalid request line";
9260 case Error::InvalidHTTPMethod: return "Invalid HTTP method";
9261 case Error::InvalidHTTPVersion: return "Invalid HTTP version";
9262 case Error::InvalidHeaders: return "Invalid headers";
9263 case Error::MultipartParsing: return "Multipart parsing failed";
9264 case Error::OpenFile: return "Failed to open file";
9265 case Error::Listen: return "Failed to listen on socket";
9266 case Error::GetSockName: return "Failed to get socket name";
9267 case Error::UnsupportedAddressFamily: return "Unsupported address family";
9268 case Error::HTTPParsing: return "HTTP parsing failed";
9269 case Error::InvalidRangeHeader: return "Invalid Range header";
9270 default: break;
9271 }
9272
9273 return "Invalid";
9274}
9275
9276inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
9277 os << to_string(obj);
9278 os << " (" << static_cast<std::underlying_type<Error>::type>(obj) << ')';
9279 return os;
9280}
9281
9282inline std::string hosted_at(const std::string &hostname) {
9283 std::vector<std::string> addrs;
9284 hosted_at(hostname, addrs);
9285 if (addrs.empty()) { return std::string(); }
9286 return addrs[0];
9287}
9288
9289inline void hosted_at(const std::string &hostname,
9290 std::vector<std::string> &addrs) {
9291 struct addrinfo hints;
9292 struct addrinfo *result;
9293
9294 memset(&hints, 0, sizeof(struct addrinfo));
9295 hints.ai_family = AF_UNSPEC;
9296 hints.ai_socktype = SOCK_STREAM;
9297 hints.ai_protocol = 0;
9298
9299 if (detail::getaddrinfo_with_timeout(hostname.c_str(), nullptr, &hints,
9300 &result, 0)) {
9301#if defined __linux__ && !defined __ANDROID__
9302 res_init();
9303#endif
9304 return;
9305 }
9306 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
9307
9308 for (auto rp = result; rp; rp = rp->ai_next) {
9309 const auto &addr =
9310 *reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
9311 std::string ip;
9312 auto dummy = -1;
9313 if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
9314 dummy)) {
9315 addrs.emplace_back(std::move(ip));
9316 }
9317 }
9318}
9319
9320inline std::string encode_uri_component(const std::string &value) {
9321 std::ostringstream escaped;
9322 escaped.fill('0');
9323 escaped << std::hex;
9324
9325 for (auto c : value) {
9326 if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
9327 c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
9328 c == ')') {
9329 escaped << c;
9330 } else {
9331 escaped << std::uppercase;
9332 escaped << '%' << std::setw(2)
9333 << static_cast<int>(static_cast<unsigned char>(c));
9334 escaped << std::nouppercase;
9335 }
9336 }
9337
9338 return escaped.str();
9339}
9340
9341inline std::string encode_uri(const std::string &value) {
9342 std::ostringstream escaped;
9343 escaped.fill('0');
9344 escaped << std::hex;
9345
9346 for (auto c : value) {
9347 if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
9348 c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
9349 c == ')' || c == ';' || c == '/' || c == '?' || c == ':' || c == '@' ||
9350 c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || c == '#') {
9351 escaped << c;
9352 } else {
9353 escaped << std::uppercase;
9354 escaped << '%' << std::setw(2)
9355 << static_cast<int>(static_cast<unsigned char>(c));
9356 escaped << std::nouppercase;
9357 }
9358 }
9359
9360 return escaped.str();
9361}
9362
9363inline std::string decode_uri_component(const std::string &value) {
9364 std::string result;
9365
9366 for (size_t i = 0; i < value.size(); i++) {
9367 if (value[i] == '%' && i + 2 < value.size()) {
9368 auto val = 0;
9369 if (detail::from_hex_to_i(value, i + 1, 2, val)) {
9370 result += static_cast<char>(val);
9371 i += 2;
9372 } else {
9373 result += value[i];
9374 }
9375 } else {
9376 result += value[i];
9377 }
9378 }
9379
9380 return result;
9381}
9382
9383inline std::string decode_uri(const std::string &value) {
9384 std::string result;
9385
9386 for (size_t i = 0; i < value.size(); i++) {
9387 if (value[i] == '%' && i + 2 < value.size()) {
9388 auto val = 0;
9389 if (detail::from_hex_to_i(value, i + 1, 2, val)) {
9390 result += static_cast<char>(val);
9391 i += 2;
9392 } else {
9393 result += value[i];
9394 }
9395 } else {
9396 result += value[i];
9397 }
9398 }
9399
9400 return result;
9401}
9402
9403inline std::string encode_path_component(const std::string &component) {
9404 std::string result;
9405 result.reserve(component.size() * 3);
9406
9407 for (size_t i = 0; i < component.size(); i++) {
9408 auto c = static_cast<unsigned char>(component[i]);
9409
9410 // Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~"
9411 if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
9412 result += static_cast<char>(c);
9413 }
9414 // Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
9415 // "," / ";" / "="
9416 else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
9417 c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
9418 c == '=') {
9419 result += static_cast<char>(c);
9420 }
9421 // Colon is allowed in path segments except first segment
9422 else if (c == ':') {
9423 result += static_cast<char>(c);
9424 }
9425 // @ is allowed in path
9426 else if (c == '@') {
9427 result += static_cast<char>(c);
9428 } else {
9429 result += '%';
9430 char hex[3];
9431 snprintf(hex, sizeof(hex), "%02X", c);
9432 result.append(hex, 2);
9433 }
9434 }
9435 return result;
9436}
9437
9438inline std::string decode_path_component(const std::string &component) {
9439 std::string result;
9440 result.reserve(component.size());
9441
9442 for (size_t i = 0; i < component.size(); i++) {
9443 if (component[i] == '%' && i + 1 < component.size()) {
9444 if (component[i + 1] == 'u') {
9445 // Unicode %uXXXX encoding
9446 auto val = 0;
9447 if (detail::from_hex_to_i(component, i + 2, 4, val)) {
9448 // 4 digits Unicode codes: val is 0x0000-0xFFFF (from 4 hex digits),
9449 // so to_utf8 writes at most 3 bytes. buff[4] is safe.
9450 char buff[4];
9451 size_t len = detail::to_utf8(val, buff);
9452 if (len > 0) { result.append(buff, len); }
9453 i += 5; // 'u0000'
9454 } else {
9455 result += component[i];
9456 }
9457 } else {
9458 // Standard %XX encoding
9459 auto val = 0;
9460 if (detail::from_hex_to_i(component, i + 1, 2, val)) {
9461 // 2 digits hex codes
9462 result += static_cast<char>(val);
9463 i += 2; // 'XX'
9464 } else {
9465 result += component[i];
9466 }
9467 }
9468 } else {
9469 result += component[i];
9470 }
9471 }
9472 return result;
9473}
9474
9475inline std::string encode_query_component(const std::string &component,
9476 bool space_as_plus) {
9477 std::string result;
9478 result.reserve(component.size() * 3);
9479
9480 for (size_t i = 0; i < component.size(); i++) {
9481 auto c = static_cast<unsigned char>(component[i]);
9482
9483 // Unreserved characters per RFC 3986
9484 if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
9485 result += static_cast<char>(c);
9486 }
9487 // Space handling
9488 else if (c == ' ') {
9489 if (space_as_plus) {
9490 result += '+';
9491 } else {
9492 result += "%20";
9493 }
9494 }
9495 // Plus sign handling
9496 else if (c == '+') {
9497 if (space_as_plus) {
9498 result += "%2B";
9499 } else {
9500 result += static_cast<char>(c);
9501 }
9502 }
9503 // Query-safe sub-delimiters (excluding & and = which are query delimiters)
9504 else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' ||
9505 c == '*' || c == ',' || c == ';') {
9506 result += static_cast<char>(c);
9507 }
9508 // Colon and @ are allowed in query
9509 else if (c == ':' || c == '@') {
9510 result += static_cast<char>(c);
9511 }
9512 // Forward slash is allowed in query values
9513 else if (c == '/') {
9514 result += static_cast<char>(c);
9515 }
9516 // Question mark is allowed in query values (after first ?)
9517 else if (c == '?') {
9518 result += static_cast<char>(c);
9519 } else {
9520 result += '%';
9521 char hex[3];
9522 snprintf(hex, sizeof(hex), "%02X", c);
9523 result.append(hex, 2);
9524 }
9525 }
9526 return result;
9527}
9528
9529inline std::string decode_query_component(const std::string &component,
9530 bool plus_as_space) {
9531 std::string result;
9532 result.reserve(component.size());
9533
9534 for (size_t i = 0; i < component.size(); i++) {
9535 if (component[i] == '%' && i + 2 < component.size()) {
9536 std::string hex = component.substr(i + 1, 2);
9537 char *end;
9538 unsigned long value = std::strtoul(hex.c_str(), &end, 16);
9539 if (end == hex.c_str() + 2) {
9540 result += static_cast<char>(value);
9541 i += 2;
9542 } else {
9543 result += component[i];
9544 }
9545 } else if (component[i] == '+' && plus_as_space) {
9546 result += ' '; // + becomes space in form-urlencoded
9547 } else {
9548 result += component[i];
9549 }
9550 }
9551 return result;
9552}
9553
9554inline std::string sanitize_filename(const std::string &filename) {
9555 // Extract basename: find the last path separator (/ or \‍)
9556 auto pos = filename.find_last_of("/\\");
9557 auto result =
9558 (pos != std::string::npos) ? filename.substr(pos + 1) : filename;
9559
9560 // Strip null bytes
9561 result.erase(std::remove(result.begin(), result.end(), '\0'), result.end());
9562
9563 // Trim whitespace
9564 {
9565 auto start = result.find_first_not_of(" \t");
9566 auto end = result.find_last_not_of(" \t");
9567 result = (start == std::string::npos)
9568 ? ""
9569 : result.substr(start, end - start + 1);
9570 }
9571
9572 // Reject . and ..
9573 if (result == "." || result == "..") { return ""; }
9574
9575 return result;
9576}
9577
9578inline std::string append_query_params(const std::string &path,
9579 const Params &params) {
9580 std::string path_with_query = path;
9581 thread_local const std::regex re("[^?]+\\?.*");
9582 auto delm = std::regex_match(path, re) ? '&' : '?';
9583 path_with_query += delm + detail::params_to_query_str(params);
9584 return path_with_query;
9585}
9586
9587// Header utilities
9588inline std::pair<std::string, std::string>
9590 std::string field = "bytes=";
9591 auto i = 0;
9592 for (const auto &r : ranges) {
9593 if (i != 0) { field += ", "; }
9594 if (r.first != -1) { field += std::to_string(r.first); }
9595 field += '-';
9596 if (r.second != -1) { field += std::to_string(r.second); }
9597 i++;
9598 }
9599 return std::make_pair("Range", std::move(field));
9600}
9601
9602inline std::pair<std::string, std::string>
9603make_basic_authentication_header(const std::string &username,
9604 const std::string &password, bool is_proxy) {
9605 auto field = "Basic " + detail::base64_encode(username + ":" + password);
9606 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
9607 return std::make_pair(key, std::move(field));
9608}
9609
9610inline std::pair<std::string, std::string>
9612 bool is_proxy = false) {
9613 auto field = "Bearer " + token;
9614 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
9615 return std::make_pair(key, std::move(field));
9616}
9617
9618// Request implementation
9619inline size_t Request::get_header_value_u64(const std::string &key, size_t def,
9620 size_t id) const {
9621 return detail::get_header_value_u64(headers, key, def, id);
9622}
9623
9624inline bool Request::has_header(const std::string &key) const {
9625 return detail::has_header(headers, key);
9626}
9627
9628inline std::string Request::get_header_value(const std::string &key,
9629 const char *def, size_t id) const {
9630 return detail::get_header_value(headers, key, def, id);
9631}
9632
9633inline size_t Request::get_header_value_count(const std::string &key) const {
9634 auto r = headers.equal_range(key);
9635 return static_cast<size_t>(std::distance(r.first, r.second));
9636}
9637
9638inline void Request::set_header(const std::string &key,
9639 const std::string &val) {
9642 headers.emplace(key, val);
9643 }
9644}
9645
9646inline bool Request::has_trailer(const std::string &key) const {
9647 return trailers.find(key) != trailers.end();
9648}
9649
9650inline std::string Request::get_trailer_value(const std::string &key,
9651 size_t id) const {
9652 auto rng = trailers.equal_range(key);
9653 auto it = rng.first;
9654 std::advance(it, static_cast<ssize_t>(id));
9655 if (it != rng.second) { return it->second; }
9656 return std::string();
9657}
9658
9659inline size_t Request::get_trailer_value_count(const std::string &key) const {
9660 auto r = trailers.equal_range(key);
9661 return static_cast<size_t>(std::distance(r.first, r.second));
9662}
9663
9664inline bool Request::has_param(const std::string &key) const {
9665 return params.find(key) != params.end();
9666}
9667
9668inline std::string Request::get_param_value(const std::string &key,
9669 size_t id) const {
9670 auto rng = params.equal_range(key);
9671 auto it = rng.first;
9672 std::advance(it, static_cast<ssize_t>(id));
9673 if (it != rng.second) { return it->second; }
9674 return std::string();
9675}
9676
9677inline size_t Request::get_param_value_count(const std::string &key) const {
9678 auto r = params.equal_range(key);
9679 return static_cast<size_t>(std::distance(r.first, r.second));
9680}
9681
9683 const auto &content_type = get_header_value("Content-Type");
9684 return detail::extract_media_type(content_type) == "multipart/form-data";
9685}
9686
9687// Multipart FormData implementation
9688inline std::string MultipartFormData::get_field(const std::string &key,
9689 size_t id) const {
9690 auto rng = fields.equal_range(key);
9691 auto it = rng.first;
9692 std::advance(it, static_cast<ssize_t>(id));
9693 if (it != rng.second) { return it->second.content; }
9694 return std::string();
9695}
9696
9697inline std::vector<std::string>
9698MultipartFormData::get_fields(const std::string &key) const {
9699 std::vector<std::string> values;
9700 auto rng = fields.equal_range(key);
9701 for (auto it = rng.first; it != rng.second; it++) {
9702 values.push_back(it->second.content);
9703 }
9704 return values;
9705}
9706
9707inline bool MultipartFormData::has_field(const std::string &key) const {
9708 return fields.find(key) != fields.end();
9709}
9710
9711inline size_t MultipartFormData::get_field_count(const std::string &key) const {
9712 auto r = fields.equal_range(key);
9713 return static_cast<size_t>(std::distance(r.first, r.second));
9714}
9715
9716inline FormData MultipartFormData::get_file(const std::string &key,
9717 size_t id) const {
9718 auto rng = files.equal_range(key);
9719 auto it = rng.first;
9720 std::advance(it, static_cast<ssize_t>(id));
9721 if (it != rng.second) { return it->second; }
9722 return FormData();
9723}
9724
9725inline std::vector<FormData>
9726MultipartFormData::get_files(const std::string &key) const {
9727 std::vector<FormData> values;
9728 auto rng = files.equal_range(key);
9729 for (auto it = rng.first; it != rng.second; it++) {
9730 values.push_back(it->second);
9731 }
9732 return values;
9733}
9734
9735inline bool MultipartFormData::has_file(const std::string &key) const {
9736 return files.find(key) != files.end();
9737}
9738
9739inline size_t MultipartFormData::get_file_count(const std::string &key) const {
9740 auto r = files.equal_range(key);
9741 return static_cast<size_t>(std::distance(r.first, r.second));
9742}
9743
9744// Response implementation
9745inline size_t Response::get_header_value_u64(const std::string &key, size_t def,
9746 size_t id) const {
9747 return detail::get_header_value_u64(headers, key, def, id);
9748}
9749
9750inline bool Response::has_header(const std::string &key) const {
9751 return headers.find(key) != headers.end();
9752}
9753
9754inline std::string Response::get_header_value(const std::string &key,
9755 const char *def,
9756 size_t id) const {
9757 return detail::get_header_value(headers, key, def, id);
9758}
9759
9760inline size_t Response::get_header_value_count(const std::string &key) const {
9761 auto r = headers.equal_range(key);
9762 return static_cast<size_t>(std::distance(r.first, r.second));
9763}
9764
9765inline void Response::set_header(const std::string &key,
9766 const std::string &val) {
9769 headers.emplace(key, val);
9770 }
9771}
9772inline bool Response::has_trailer(const std::string &key) const {
9773 return trailers.find(key) != trailers.end();
9774}
9775
9776inline std::string Response::get_trailer_value(const std::string &key,
9777 size_t id) const {
9778 auto rng = trailers.equal_range(key);
9779 auto it = rng.first;
9780 std::advance(it, static_cast<ssize_t>(id));
9781 if (it != rng.second) { return it->second; }
9782 return std::string();
9783}
9784
9785inline size_t Response::get_trailer_value_count(const std::string &key) const {
9786 auto r = trailers.equal_range(key);
9787 return static_cast<size_t>(std::distance(r.first, r.second));
9788}
9789
9790inline void Response::set_redirect(const std::string &url, int stat) {
9792 set_header("Location", url);
9793 if (300 <= stat && stat < 400) {
9794 this->status = stat;
9795 } else {
9797 }
9798 }
9799}
9800
9801inline void Response::set_content(const char *s, size_t n,
9802 const std::string &content_type) {
9803 body.assign(s, n);
9804
9805 auto rng = headers.equal_range("Content-Type");
9806 headers.erase(rng.first, rng.second);
9807 set_header("Content-Type", content_type);
9808}
9809
9810inline void Response::set_content(const std::string &s,
9811 const std::string &content_type) {
9812 set_content(s.data(), s.size(), content_type);
9813}
9814
9815inline void Response::set_content(std::string &&s,
9816 const std::string &content_type) {
9817 body = std::move(s);
9818
9819 auto rng = headers.equal_range("Content-Type");
9820 headers.erase(rng.first, rng.second);
9821 set_header("Content-Type", content_type);
9822}
9823
9825 size_t in_length, const std::string &content_type, ContentProvider provider,
9826 ContentProviderResourceReleaser resource_releaser) {
9827 set_header("Content-Type", content_type);
9828 content_length_ = in_length;
9829 if (in_length > 0) { content_provider_ = std::move(provider); }
9830 content_provider_resource_releaser_ = std::move(resource_releaser);
9832}
9833
9835 const std::string &content_type, ContentProviderWithoutLength provider,
9836 ContentProviderResourceReleaser resource_releaser) {
9837 set_header("Content-Type", content_type);
9838 content_length_ = 0;
9840 content_provider_resource_releaser_ = std::move(resource_releaser);
9842}
9843
9845 const std::string &content_type, ContentProviderWithoutLength provider,
9846 ContentProviderResourceReleaser resource_releaser) {
9847 set_header("Content-Type", content_type);
9848 content_length_ = 0;
9850 content_provider_resource_releaser_ = std::move(resource_releaser);
9852}
9853
9854inline void Response::set_file_content(const std::string &path,
9855 const std::string &content_type) {
9856 file_content_path_ = path;
9857 file_content_content_type_ = content_type;
9858}
9859
9860inline void Response::set_file_content(const std::string &path) {
9861 file_content_path_ = path;
9862}
9863
9864// Result implementation
9865inline size_t Result::get_request_header_value_u64(const std::string &key,
9866 size_t def,
9867 size_t id) const {
9868 return detail::get_header_value_u64(request_headers_, key, def, id);
9869}
9870
9871inline bool Result::has_request_header(const std::string &key) const {
9872 return request_headers_.find(key) != request_headers_.end();
9873}
9874
9875inline std::string Result::get_request_header_value(const std::string &key,
9876 const char *def,
9877 size_t id) const {
9878 return detail::get_header_value(request_headers_, key, def, id);
9879}
9880
9881inline size_t
9882Result::get_request_header_value_count(const std::string &key) const {
9883 auto r = request_headers_.equal_range(key);
9884 return static_cast<size_t>(std::distance(r.first, r.second));
9885}
9886
9887// Stream implementation
9888inline ssize_t Stream::write(const char *ptr) {
9889 return write(ptr, strlen(ptr));
9890}
9891
9892inline ssize_t Stream::write(const std::string &s) {
9893 return write(s.data(), s.size());
9894}
9895
9896// BodyReader implementation
9897inline ssize_t detail::BodyReader::read(char *buf, size_t len) {
9898 if (!stream) {
9900 return -1;
9901 }
9902 if (eof) { return 0; }
9903
9904 if (!chunked) {
9905 // Content-Length based reading
9907 eof = true;
9908 return 0;
9909 }
9910
9911 auto to_read = len;
9912 if (has_content_length) {
9913 auto remaining = content_length - bytes_read;
9914 to_read = (std::min)(len, remaining);
9915 }
9916 auto n = stream->read(buf, to_read);
9917
9918 if (n < 0) {
9919 last_error = stream->get_error();
9921 eof = true;
9922 return n;
9923 }
9924 if (n == 0) {
9925 // Unexpected EOF before content_length
9926 last_error = stream->get_error();
9928 eof = true;
9929 return 0;
9930 }
9931
9932 bytes_read += static_cast<size_t>(n);
9933 if (has_content_length && bytes_read >= content_length) { eof = true; }
9936 eof = true;
9937 return -1;
9938 }
9939 return n;
9940 }
9941
9942 // Chunked transfer encoding: delegate to shared decoder instance.
9944
9945 size_t chunk_offset = 0;
9946 size_t chunk_total = 0;
9947 auto n = chunked_decoder->read_payload(buf, len, chunk_offset, chunk_total);
9948 if (n < 0) {
9949 last_error = stream->get_error();
9951 eof = true;
9952 return n;
9953 }
9954
9955 if (n == 0) {
9956 // Final chunk observed. Leave trailer parsing to the caller (StreamHandle).
9957 eof = true;
9958 return 0;
9959 }
9960
9961 bytes_read += static_cast<size_t>(n);
9964 eof = true;
9965 return -1;
9966 }
9967 return n;
9968}
9969
9970// ThreadPool implementation
9971inline ThreadPool::ThreadPool(size_t n, size_t max_n, size_t mqr)
9972 : base_thread_count_(n), max_queued_requests_(mqr), idle_thread_count_(0),
9973 shutdown_(false) {
9974#ifndef CPPHTTPLIB_NO_EXCEPTIONS
9975 if (max_n != 0 && max_n < n) {
9976 std::string msg = "max_threads must be >= base_threads";
9977 throw std::invalid_argument(msg);
9978 }
9979#endif
9980 max_thread_count_ = max_n == 0 ? n : max_n;
9981 threads_.reserve(base_thread_count_);
9982 for (size_t i = 0; i < base_thread_count_; i++) {
9983 threads_.emplace_back(std::thread([this]() { worker(false); }));
9984 }
9985}
9986
9987inline bool ThreadPool::enqueue(std::function<void()> fn) {
9988 {
9989 std::unique_lock<std::mutex> lock(mutex_);
9990 if (shutdown_) { return false; }
9991 if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
9992 return false;
9993 }
9994 jobs_.push_back(std::move(fn));
9995
9996 // Spawn a dynamic thread if no idle threads and under max
9997 if (idle_thread_count_ == 0 &&
9998 threads_.size() + dynamic_threads_.size() < max_thread_count_) {
9999 cleanup_finished_threads();
10000 dynamic_threads_.emplace_back(std::thread([this]() { worker(true); }));
10001 }
10002 }
10003
10004 cond_.notify_one();
10005 return true;
10006}
10007
10009 {
10010 std::unique_lock<std::mutex> lock(mutex_);
10011 shutdown_ = true;
10012 }
10013
10014 cond_.notify_all();
10015
10016 for (auto &t : threads_) {
10017 if (t.joinable()) { t.join(); }
10018 }
10019
10020 // Move dynamic_threads_ to a local list under the lock to avoid racing
10021 // with worker threads that call move_to_finished() concurrently.
10022 std::list<std::thread> remaining_dynamic;
10023 {
10024 std::unique_lock<std::mutex> lock(mutex_);
10025 remaining_dynamic = std::move(dynamic_threads_);
10026 }
10027 for (auto &t : remaining_dynamic) {
10028 if (t.joinable()) { t.join(); }
10029 }
10030
10031 std::unique_lock<std::mutex> lock(mutex_);
10032 cleanup_finished_threads();
10033}
10034
10035inline void ThreadPool::move_to_finished(std::thread::id id) {
10036 // Must be called with mutex_ held
10037 for (auto it = dynamic_threads_.begin(); it != dynamic_threads_.end(); ++it) {
10038 if (it->get_id() == id) {
10039 finished_threads_.push_back(std::move(*it));
10040 dynamic_threads_.erase(it);
10041 return;
10042 }
10043 }
10044}
10045
10046inline void ThreadPool::cleanup_finished_threads() {
10047 // Must be called with mutex_ held
10048 for (auto &t : finished_threads_) {
10049 if (t.joinable()) { t.join(); }
10050 }
10051 finished_threads_.clear();
10052}
10053
10054inline void ThreadPool::worker(bool is_dynamic) {
10055 for (;;) {
10056 std::function<void()> fn;
10057 {
10058 std::unique_lock<std::mutex> lock(mutex_);
10059 idle_thread_count_++;
10060
10061 if (is_dynamic) {
10062 auto has_work = cond_.wait_for(
10063 lock, std::chrono::seconds(CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT),
10064 [&] { return !jobs_.empty() || shutdown_; });
10065 if (!has_work) {
10066 // Timed out with no work - exit this dynamic thread
10067 idle_thread_count_--;
10068 move_to_finished(std::this_thread::get_id());
10069 break;
10070 }
10071 } else {
10072 cond_.wait(lock, [&] { return !jobs_.empty() || shutdown_; });
10073 }
10074
10075 idle_thread_count_--;
10076
10077 if (shutdown_ && jobs_.empty()) { break; }
10078
10079 fn = std::move(jobs_.front());
10080 jobs_.pop_front();
10081 }
10082
10083 assert(true == static_cast<bool>(fn));
10084 fn();
10085
10086 // Dynamic thread: exit if queue is empty after task completion
10087 if (is_dynamic) {
10088 std::unique_lock<std::mutex> lock(mutex_);
10089 if (jobs_.empty()) {
10090 move_to_finished(std::this_thread::get_id());
10091 break;
10092 }
10093 }
10094 }
10095
10096#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \
10097 !defined(LIBRESSL_VERSION_NUMBER)
10098 OPENSSL_thread_stop();
10099#endif
10100}
10101
10102/*
10103 * Group 1 (continued): detail namespace - Stream implementations
10104 */
10105
10106namespace detail {
10107
10108inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec,
10109 time_t timeout_sec, time_t timeout_usec,
10110 time_t &actual_timeout_sec,
10111 time_t &actual_timeout_usec) {
10112 auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
10113
10114 auto actual_timeout_msec =
10115 (std::min)(max_timeout_msec - duration_msec, timeout_msec);
10116
10117 if (actual_timeout_msec < 0) { actual_timeout_msec = 0; }
10118
10119 actual_timeout_sec = actual_timeout_msec / 1000;
10120 actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
10121}
10122
10123// Socket stream implementation
10125 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
10126 time_t write_timeout_sec, time_t write_timeout_usec,
10127 time_t max_timeout_msec,
10128 std::chrono::time_point<std::chrono::steady_clock> start_time)
10129 : sock_(sock), read_timeout_sec_(read_timeout_sec),
10130 read_timeout_usec_(read_timeout_usec),
10131 write_timeout_sec_(write_timeout_sec),
10132 write_timeout_usec_(write_timeout_usec),
10133 max_timeout_msec_(max_timeout_msec), start_time_(start_time),
10134 read_buff_(read_buff_size_, 0) {}
10135
10136inline SocketStream::~SocketStream() = default;
10137
10138inline bool SocketStream::is_readable() const {
10139 return read_buff_off_ < read_buff_content_size_;
10140}
10141
10142inline bool SocketStream::wait_readable() const {
10143 if (max_timeout_msec_ <= 0) {
10144 return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
10145 }
10146
10147 time_t read_timeout_sec;
10148 time_t read_timeout_usec;
10149 calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
10150 read_timeout_usec_, read_timeout_sec, read_timeout_usec);
10151
10152 return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
10153}
10154
10155inline bool SocketStream::wait_writable() const {
10156 return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
10157}
10158
10159inline bool SocketStream::is_peer_alive() const {
10160 return detail::is_socket_alive(sock_);
10161}
10162
10163inline ssize_t SocketStream::read(char *ptr, size_t size) {
10164#ifdef _WIN32
10165 size =
10166 (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
10167#else
10168 size = (std::min)(size,
10169 static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
10170#endif
10171
10172 if (read_buff_off_ < read_buff_content_size_) {
10173 auto remaining_size = read_buff_content_size_ - read_buff_off_;
10174 if (size <= remaining_size) {
10175 memcpy(ptr, read_buff_.data() + read_buff_off_, size);
10176 read_buff_off_ += size;
10177 return static_cast<ssize_t>(size);
10178 } else {
10179 memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size);
10180 read_buff_off_ += remaining_size;
10181 return static_cast<ssize_t>(remaining_size);
10182 }
10183 }
10184
10185 if (!wait_readable()) {
10187 return -1;
10188 }
10189
10190 read_buff_off_ = 0;
10191 read_buff_content_size_ = 0;
10192
10193 if (size < read_buff_size_) {
10194 auto n = read_socket(sock_, read_buff_.data(), read_buff_size_,
10196 if (n <= 0) {
10197 if (n == 0) {
10199 } else {
10201 }
10202 return n;
10203 } else if (n <= static_cast<ssize_t>(size)) {
10204 memcpy(ptr, read_buff_.data(), static_cast<size_t>(n));
10205 return n;
10206 } else {
10207 memcpy(ptr, read_buff_.data(), size);
10208 read_buff_off_ = size;
10209 read_buff_content_size_ = static_cast<size_t>(n);
10210 return static_cast<ssize_t>(size);
10211 }
10212 } else {
10213 auto n = read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
10214 if (n <= 0) {
10215 if (n == 0) {
10217 } else {
10219 }
10220 }
10221 return n;
10222 }
10223}
10224
10225inline ssize_t SocketStream::write(const char *ptr, size_t size) {
10226 if (!wait_writable()) { return -1; }
10227
10228#if defined(_WIN32) && !defined(_WIN64)
10229 size =
10230 (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
10231#endif
10232
10233 return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
10234}
10235
10236inline void SocketStream::get_remote_ip_and_port(std::string &ip,
10237 int &port) const {
10238 return detail::get_remote_ip_and_port(sock_, ip, port);
10239}
10240
10241inline void SocketStream::get_local_ip_and_port(std::string &ip,
10242 int &port) const {
10243 return detail::get_local_ip_and_port(sock_, ip, port);
10244}
10245
10246inline socket_t SocketStream::socket() const { return sock_; }
10247
10248inline time_t SocketStream::duration() const {
10249 return std::chrono::duration_cast<std::chrono::milliseconds>(
10250 std::chrono::steady_clock::now() - start_time_)
10251 .count();
10252}
10253
10254inline void SocketStream::set_read_timeout(time_t sec, time_t usec) {
10255 read_timeout_sec_ = sec;
10256 read_timeout_usec_ = usec;
10257}
10258
10259// Buffer stream implementation
10260inline bool BufferStream::is_readable() const { return true; }
10261
10262inline bool BufferStream::wait_readable() const { return true; }
10263
10264inline bool BufferStream::wait_writable() const { return true; }
10265
10266inline ssize_t BufferStream::read(char *ptr, size_t size) {
10267#if defined(_MSC_VER) && _MSC_VER < 1910
10268 auto len_read = buffer._Copy_s(ptr, size, size, position);
10269#else
10270 auto len_read = buffer.copy(ptr, size, position);
10271#endif
10272 position += static_cast<size_t>(len_read);
10273 return static_cast<ssize_t>(len_read);
10274}
10275
10276inline ssize_t BufferStream::write(const char *ptr, size_t size) {
10277 buffer.append(ptr, size);
10278 return static_cast<ssize_t>(size);
10279}
10280
10281inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
10282 int & /*port*/) const {}
10283
10284inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
10285 int & /*port*/) const {}
10286
10287inline socket_t BufferStream::socket() const { return 0; }
10288
10289inline time_t BufferStream::duration() const { return 0; }
10290
10291inline const std::string &BufferStream::get_buffer() const { return buffer; }
10292
10294 : MatcherBase(pattern) {
10295 constexpr const char marker[] = "/:";
10296
10297 // One past the last ending position of a path param substring
10298 std::size_t last_param_end = 0;
10299
10300#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10301 // Needed to ensure that parameter names are unique during matcher
10302 // construction
10303 // If exceptions are disabled, only last duplicate path
10304 // parameter will be set
10305 std::unordered_set<std::string> param_name_set;
10306#endif
10307
10308 while (true) {
10309 const auto marker_pos = pattern.find(
10310 marker, last_param_end == 0 ? last_param_end : last_param_end - 1);
10311 if (marker_pos == std::string::npos) { break; }
10312
10313 static_fragments_.push_back(
10314 pattern.substr(last_param_end, marker_pos - last_param_end + 1));
10315
10316 const auto param_name_start = marker_pos + str_len(marker);
10317
10318 auto sep_pos = pattern.find(separator, param_name_start);
10319 if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
10320
10321 auto param_name =
10322 pattern.substr(param_name_start, sep_pos - param_name_start);
10323
10324#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10325 if (param_name_set.find(param_name) != param_name_set.cend()) {
10326 std::string msg = "Encountered path parameter '" + param_name +
10327 "' multiple times in route pattern '" + pattern + "'.";
10328 throw std::invalid_argument(msg);
10329 }
10330#endif
10331
10332 param_names_.push_back(std::move(param_name));
10333
10334 last_param_end = sep_pos + 1;
10335 }
10336
10337 if (last_param_end < pattern.length()) {
10338 static_fragments_.push_back(pattern.substr(last_param_end));
10339 }
10340}
10341
10342inline bool PathParamsMatcher::match(Request &request) const {
10343 request.matches = std::smatch();
10344 request.path_params.clear();
10345 request.path_params.reserve(param_names_.size());
10346
10347 // One past the position at which the path matched the pattern last time
10348 std::size_t starting_pos = 0;
10349 for (size_t i = 0; i < static_fragments_.size(); ++i) {
10350 const auto &fragment = static_fragments_[i];
10351
10352 if (starting_pos + fragment.length() > request.path.length()) {
10353 return false;
10354 }
10355
10356 // Avoid unnecessary allocation by using strncmp instead of substr +
10357 // comparison
10358 if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(),
10359 fragment.length()) != 0) {
10360 return false;
10361 }
10362
10363 starting_pos += fragment.length();
10364
10365 // Should only happen when we have a static fragment after a param
10366 // Example: '/users/:id/subscriptions'
10367 // The 'subscriptions' fragment here does not have a corresponding param
10368 if (i >= param_names_.size()) { continue; }
10369
10370 auto sep_pos = request.path.find(separator, starting_pos);
10371 if (sep_pos == std::string::npos) { sep_pos = request.path.length(); }
10372
10373 const auto &param_name = param_names_[i];
10374
10375 request.path_params.emplace(
10376 param_name, request.path.substr(starting_pos, sep_pos - starting_pos));
10377
10378 // Mark everything up to '/' as matched
10379 starting_pos = sep_pos + 1;
10380 }
10381 // Returns false if the path is longer than the pattern
10382 return starting_pos >= request.path.length();
10383}
10384
10385inline bool RegexMatcher::match(Request &request) const {
10386 request.path_params.clear();
10387 return std::regex_match(request.path, request.matches, regex_);
10388}
10389
10390// Enclose IPv6 address in brackets if needed
10391inline std::string prepare_host_string(const std::string &host) {
10392 // Enclose IPv6 address in brackets (but not if already enclosed)
10393 if (host.find(':') == std::string::npos ||
10394 (!host.empty() && host[0] == '[')) {
10395 // IPv4, hostname, or already bracketed IPv6
10396 return host;
10397 } else {
10398 // IPv6 address without brackets
10399 return "[" + host + "]";
10400 }
10401}
10402
10403inline std::string make_host_and_port_string(const std::string &host, int port,
10404 bool is_ssl) {
10405 auto result = prepare_host_string(host);
10406
10407 // Append port if not default
10408 if ((!is_ssl && port == 80) || (is_ssl && port == 443)) {
10409 ; // do nothing
10410 } else {
10411 result += ":" + std::to_string(port);
10412 }
10413
10414 return result;
10415}
10416
10417// Create "host:port" string always including port number (for CONNECT method)
10418inline std::string
10419make_host_and_port_string_always_port(const std::string &host, int port) {
10420 return prepare_host_string(host) + ":" + std::to_string(port);
10421}
10422
10423template <typename T>
10424inline bool check_and_write_headers(Stream &strm, Headers &headers,
10425 T header_writer, Error &error) {
10426 for (const auto &h : headers) {
10427 if (!detail::fields::is_field_name(h.first) ||
10428 !detail::fields::is_field_value(h.second)) {
10429 error = Error::InvalidHeaders;
10430 return false;
10431 }
10432 }
10433 if (header_writer(strm, headers) <= 0) {
10434 error = Error::Write;
10435 return false;
10436 }
10437 return true;
10438}
10439
10440} // namespace detail
10441
10442/*
10443 * Group 2 (continued): detail namespace - SSLSocketStream implementation
10444 */
10445
10446#ifdef CPPHTTPLIB_SSL_ENABLED
10447namespace detail {
10448
10449// SSL socket stream implementation
10450inline SSLSocketStream::SSLSocketStream(
10451 socket_t sock, tls::session_t session, time_t read_timeout_sec,
10452 time_t read_timeout_usec, time_t write_timeout_sec,
10453 time_t write_timeout_usec, time_t max_timeout_msec,
10454 std::chrono::time_point<std::chrono::steady_clock> start_time)
10455 : sock_(sock), session_(session), read_timeout_sec_(read_timeout_sec),
10456 read_timeout_usec_(read_timeout_usec),
10457 write_timeout_sec_(write_timeout_sec),
10458 write_timeout_usec_(write_timeout_usec),
10459 max_timeout_msec_(max_timeout_msec), start_time_(start_time) {
10460#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
10461 // Clear AUTO_RETRY for proper non-blocking I/O timeout handling
10462 // Note: create_session() also clears this, but SSLClient currently
10463 // uses ssl_new() which does not. Until full TLS API migration is complete,
10464 // we need to ensure AUTO_RETRY is cleared here regardless of how the
10465 // SSL session was created.
10466 SSL_clear_mode(static_cast<SSL *>(session), SSL_MODE_AUTO_RETRY);
10467#endif
10468}
10469
10470inline SSLSocketStream::~SSLSocketStream() = default;
10471
10472inline bool SSLSocketStream::is_readable() const {
10473 return tls::pending(session_) > 0;
10474}
10475
10476inline bool SSLSocketStream::wait_readable() const {
10477 if (max_timeout_msec_ <= 0) {
10478 return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
10479 }
10480
10481 time_t read_timeout_sec;
10482 time_t read_timeout_usec;
10483 calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
10484 read_timeout_usec_, read_timeout_sec, read_timeout_usec);
10485
10486 return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
10487}
10488
10489inline bool SSLSocketStream::wait_writable() const {
10490 return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
10491 !tls::is_peer_closed(session_, sock_);
10492}
10493
10494inline bool SSLSocketStream::is_peer_alive() const {
10495 return !tls::is_peer_closed(session_, sock_);
10496}
10497
10498inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
10499 if (tls::pending(session_) > 0) {
10500 tls::TlsError err;
10501 auto ret = tls::read(session_, ptr, size, err);
10502 if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) {
10503 error_ = Error::ConnectionClosed;
10504 }
10505 return ret;
10506 } else if (wait_readable()) {
10507 tls::TlsError err;
10508 auto ret = tls::read(session_, ptr, size, err);
10509 if (ret < 0) {
10510 auto n = 1000;
10511#ifdef _WIN32
10512 while (--n >= 0 && (err.code == tls::ErrorCode::WantRead ||
10513 (err.code == tls::ErrorCode::SyscallError &&
10514 WSAGetLastError() == WSAETIMEDOUT))) {
10515#else
10516 while (--n >= 0 && err.code == tls::ErrorCode::WantRead) {
10517#endif
10518 if (tls::pending(session_) > 0) {
10519 return tls::read(session_, ptr, size, err);
10520 } else if (wait_readable()) {
10521 std::this_thread::sleep_for(std::chrono::microseconds{10});
10522 ret = tls::read(session_, ptr, size, err);
10523 if (ret >= 0) { return ret; }
10524 } else {
10525 break;
10526 }
10527 }
10528 assert(ret < 0);
10529 } else if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) {
10530 error_ = Error::ConnectionClosed;
10531 }
10532 return ret;
10533 } else {
10534 error_ = Error::Timeout;
10535 return -1;
10536 }
10537}
10538
10539inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
10540 if (wait_writable()) {
10541 auto handle_size =
10542 std::min<size_t>(size, (std::numeric_limits<int>::max)());
10543
10544 tls::TlsError err;
10545 auto ret = tls::write(session_, ptr, handle_size, err);
10546 if (ret < 0) {
10547 auto n = 1000;
10548#ifdef _WIN32
10549 while (--n >= 0 && (err.code == tls::ErrorCode::WantWrite ||
10550 (err.code == tls::ErrorCode::SyscallError &&
10551 WSAGetLastError() == WSAETIMEDOUT))) {
10552#else
10553 while (--n >= 0 && err.code == tls::ErrorCode::WantWrite) {
10554#endif
10555 if (wait_writable()) {
10556 std::this_thread::sleep_for(std::chrono::microseconds{10});
10557 ret = tls::write(session_, ptr, handle_size, err);
10558 if (ret >= 0) { return ret; }
10559 } else {
10560 break;
10561 }
10562 }
10563 assert(ret < 0);
10564 }
10565 return ret;
10566 }
10567 return -1;
10568}
10569
10570inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
10571 int &port) const {
10572 detail::get_remote_ip_and_port(sock_, ip, port);
10573}
10574
10575inline void SSLSocketStream::get_local_ip_and_port(std::string &ip,
10576 int &port) const {
10577 detail::get_local_ip_and_port(sock_, ip, port);
10578}
10579
10580inline socket_t SSLSocketStream::socket() const { return sock_; }
10581
10582inline time_t SSLSocketStream::duration() const {
10583 return std::chrono::duration_cast<std::chrono::milliseconds>(
10584 std::chrono::steady_clock::now() - start_time_)
10585 .count();
10586}
10587
10588inline void SSLSocketStream::set_read_timeout(time_t sec, time_t usec) {
10589 read_timeout_sec_ = sec;
10590 read_timeout_usec_ = usec;
10591}
10592
10593} // namespace detail
10594#endif // CPPHTTPLIB_SSL_ENABLED
10595
10596/*
10597 * Group 4: Server implementation
10598 */
10599
10600// HTTP server implementation
10602 : new_task_queue([] {
10605 }) {
10606#ifndef _WIN32
10607 signal(SIGPIPE, SIG_IGN);
10608#endif
10609}
10610
10611inline Server::~Server() = default;
10612
10613inline std::unique_ptr<detail::MatcherBase>
10614Server::make_matcher(const std::string &pattern) {
10615 if (pattern.find("/:") != std::string::npos) {
10617 } else {
10619 }
10620}
10621
10622inline Server &Server::Get(const std::string &pattern, Handler handler) {
10623 get_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
10624 return *this;
10625}
10626
10627inline Server &Server::Post(const std::string &pattern, Handler handler) {
10628 post_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
10629 return *this;
10630}
10631
10632inline Server &Server::Post(const std::string &pattern,
10633 HandlerWithContentReader handler) {
10634 post_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
10635 std::move(handler));
10636 return *this;
10637}
10638
10639inline Server &Server::Put(const std::string &pattern, Handler handler) {
10640 put_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
10641 return *this;
10642}
10643
10644inline Server &Server::Put(const std::string &pattern,
10645 HandlerWithContentReader handler) {
10646 put_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
10647 std::move(handler));
10648 return *this;
10649}
10650
10651inline Server &Server::Patch(const std::string &pattern, Handler handler) {
10652 patch_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
10653 return *this;
10654}
10655
10656inline Server &Server::Patch(const std::string &pattern,
10657 HandlerWithContentReader handler) {
10658 patch_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
10659 std::move(handler));
10660 return *this;
10661}
10662
10663inline Server &Server::Delete(const std::string &pattern, Handler handler) {
10664 delete_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
10665 return *this;
10666}
10667
10668inline Server &Server::Delete(const std::string &pattern,
10669 HandlerWithContentReader handler) {
10670 delete_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
10671 std::move(handler));
10672 return *this;
10673}
10674
10675inline Server &Server::Options(const std::string &pattern, Handler handler) {
10676 options_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
10677 return *this;
10678}
10679
10680inline Server &Server::WebSocket(const std::string &pattern,
10681 WebSocketHandler handler) {
10682 websocket_handlers_.push_back(
10683 {make_matcher(pattern), std::move(handler), nullptr});
10684 return *this;
10685}
10686
10687inline Server &Server::WebSocket(const std::string &pattern,
10688 WebSocketHandler handler,
10689 SubProtocolSelector sub_protocol_selector) {
10690 websocket_handlers_.push_back({make_matcher(pattern), std::move(handler),
10691 std::move(sub_protocol_selector)});
10692 return *this;
10693}
10694
10695inline bool Server::set_base_dir(const std::string &dir,
10696 const std::string &mount_point) {
10697 return set_mount_point(mount_point, dir);
10698}
10699
10700inline bool Server::set_mount_point(const std::string &mount_point,
10701 const std::string &dir, Headers headers) {
10702 detail::FileStat stat(dir);
10703 if (stat.is_dir()) {
10704 std::string mnt = !mount_point.empty() ? mount_point : "/";
10705 if (!mnt.empty() && mnt[0] == '/') {
10706 std::string resolved_base;
10707 if (detail::canonicalize_path(dir.c_str(), resolved_base)) {
10708#if defined(_WIN32)
10709 if (resolved_base.back() != '\\' && resolved_base.back() != '/') {
10710 resolved_base += '\\';
10711 }
10712#else
10713 if (resolved_base.back() != '/') { resolved_base += '/'; }
10714#endif
10715 }
10716 base_dirs_.push_back(
10717 {std::move(mnt), dir, std::move(resolved_base), std::move(headers)});
10718 return true;
10719 }
10720 }
10721 return false;
10722}
10723
10724inline bool Server::remove_mount_point(const std::string &mount_point) {
10725 for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
10726 if (it->mount_point == mount_point) {
10727 base_dirs_.erase(it);
10728 return true;
10729 }
10730 }
10731 return false;
10732}
10733
10734inline Server &
10736 const std::string &mime) {
10737 file_extension_and_mimetype_map_[ext] = mime;
10738 return *this;
10739}
10740
10741inline Server &Server::set_default_file_mimetype(const std::string &mime) {
10742 default_file_mimetype_ = mime;
10743 return *this;
10744}
10745
10747 file_request_handler_ = std::move(handler);
10748 return *this;
10749}
10750
10751inline Server &Server::set_error_handler_core(HandlerWithResponse handler,
10752 std::true_type) {
10753 error_handler_ = std::move(handler);
10754 return *this;
10755}
10756
10757inline Server &Server::set_error_handler_core(Handler handler,
10758 std::false_type) {
10759 error_handler_ = [handler](const Request &req, Response &res) {
10760 handler(req, res);
10762 };
10763 return *this;
10764}
10765
10767 exception_handler_ = std::move(handler);
10768 return *this;
10769}
10770
10772 pre_routing_handler_ = std::move(handler);
10773 return *this;
10774}
10775
10777 post_routing_handler_ = std::move(handler);
10778 return *this;
10779}
10780
10782 pre_request_handler_ = std::move(handler);
10783 return *this;
10784}
10785
10787 logger_ = std::move(logger);
10788 return *this;
10789}
10790
10792 error_logger_ = std::move(error_logger);
10793 return *this;
10794}
10795
10797 pre_compression_logger_ = std::move(logger);
10798 return *this;
10799}
10800
10801inline Server &
10803 expect_100_continue_handler_ = std::move(handler);
10804 return *this;
10805}
10806
10808 address_family_ = family;
10809 return *this;
10810}
10811
10813 tcp_nodelay_ = on;
10814 return *this;
10815}
10816
10818 ipv6_v6only_ = on;
10819 return *this;
10820}
10821
10823 socket_options_ = std::move(socket_options);
10824 return *this;
10825}
10826
10828 default_headers_ = std::move(headers);
10829 return *this;
10830}
10831
10833 std::function<ssize_t(Stream &, Headers &)> const &writer) {
10834 header_writer_ = writer;
10835 return *this;
10836}
10837
10838inline Server &
10839Server::set_trusted_proxies(const std::vector<std::string> &proxies) {
10840 trusted_proxies_ = proxies;
10841 return *this;
10842}
10843
10845 keep_alive_max_count_ = count;
10846 return *this;
10847}
10848
10851 return *this;
10852}
10853
10854inline Server &Server::set_read_timeout(time_t sec, time_t usec) {
10855 read_timeout_sec_ = sec;
10856 read_timeout_usec_ = usec;
10857 return *this;
10858}
10859
10860inline Server &Server::set_write_timeout(time_t sec, time_t usec) {
10861 write_timeout_sec_ = sec;
10862 write_timeout_usec_ = usec;
10863 return *this;
10864}
10865
10866inline Server &Server::set_idle_interval(time_t sec, time_t usec) {
10867 idle_interval_sec_ = sec;
10868 idle_interval_usec_ = usec;
10869 return *this;
10870}
10871
10873 payload_max_length_ = length;
10874 return *this;
10875}
10876
10879 return *this;
10880}
10881
10882template <class Rep, class Period>
10884 const std::chrono::duration<Rep, Period> &duration) {
10885 detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) {
10887 });
10888 return *this;
10889}
10890
10891inline bool Server::bind_to_port(const std::string &host, int port,
10892 int socket_flags) {
10893 auto ret = bind_internal(host, port, socket_flags);
10894 if (ret == -1) { is_decommissioned = true; }
10895 return ret >= 0;
10896}
10897inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
10898 auto ret = bind_internal(host, 0, socket_flags);
10899 if (ret == -1) { is_decommissioned = true; }
10900 return ret;
10901}
10902
10903inline bool Server::listen_after_bind() { return listen_internal(); }
10904
10905inline bool Server::listen(const std::string &host, int port,
10906 int socket_flags) {
10907 return bind_to_port(host, port, socket_flags) && listen_internal();
10908}
10909
10910inline bool Server::is_running() const { return is_running_; }
10911
10912inline void Server::wait_until_ready() const {
10913 while (!is_running_ && !is_decommissioned) {
10914 std::this_thread::sleep_for(std::chrono::milliseconds{1});
10915 }
10916}
10917
10918inline void Server::stop() {
10919 if (is_running_) {
10920 assert(svr_sock_ != INVALID_SOCKET);
10921 std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET));
10924 }
10925 is_decommissioned = false;
10926}
10927
10928inline void Server::decommission() { is_decommissioned = true; }
10929
10930inline bool Server::parse_request_line(const char *s, Request &req) const {
10931 auto len = strlen(s);
10932 if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
10933 len -= 2;
10934
10935 {
10936 size_t count = 0;
10937
10938 detail::split(s, s + len, ' ', [&](const char *b, const char *e) {
10939 switch (count) {
10940 case 0: req.method = std::string(b, e); break;
10941 case 1: req.target = std::string(b, e); break;
10942 case 2: req.version = std::string(b, e); break;
10943 default: break;
10944 }
10945 count++;
10946 });
10947
10948 if (count != 3) { return false; }
10949 }
10950
10951 thread_local const std::set<std::string> methods{
10952 "GET", "HEAD", "POST", "PUT", "DELETE",
10953 "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
10954
10955 if (methods.find(req.method) == methods.end()) {
10956 output_error_log(Error::InvalidHTTPMethod, &req);
10957 return false;
10958 }
10959
10960 if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") {
10961 output_error_log(Error::InvalidHTTPVersion, &req);
10962 return false;
10963 }
10964
10965 {
10966 // Skip URL fragment
10967 for (size_t i = 0; i < req.target.size(); i++) {
10968 if (req.target[i] == '#') {
10969 req.target.erase(i);
10970 break;
10971 }
10972 }
10973
10974 detail::divide(req.target, '?',
10975 [&](const char *lhs_data, std::size_t lhs_size,
10976 const char *rhs_data, std::size_t rhs_size) {
10977 req.path =
10978 decode_path_component(std::string(lhs_data, lhs_size));
10979 detail::parse_query_text(rhs_data, rhs_size, req.params);
10980 });
10981 }
10982
10983 return true;
10984}
10985
10986inline bool Server::write_response(Stream &strm, bool close_connection,
10987 Request &req, Response &res) {
10988 // NOTE: `req.ranges` should be empty, otherwise it will be applied
10989 // incorrectly to the error content.
10990 req.ranges.clear();
10991 return write_response_core(strm, close_connection, req, res, false);
10992}
10993
10994inline bool Server::write_response_with_content(Stream &strm,
10995 bool close_connection,
10996 const Request &req,
10997 Response &res) {
10998 return write_response_core(strm, close_connection, req, res, true);
10999}
11000
11001inline bool Server::write_response_core(Stream &strm, bool close_connection,
11002 const Request &req, Response &res,
11003 bool need_apply_ranges) {
11004 assert(res.status != -1);
11005
11006 if (400 <= res.status && error_handler_ &&
11007 error_handler_(req, res) == HandlerResponse::Handled) {
11008 need_apply_ranges = true;
11009 }
11010
11011 std::string content_type;
11012 std::string boundary;
11013 if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
11014
11015 // Prepare additional headers
11016 if (close_connection || req.get_header_value("Connection") == "close" ||
11017 400 <= res.status) { // Don't leave connections open after errors
11018 res.set_header("Connection", "close");
11019 } else {
11020 std::string s = "timeout=";
11021 s += std::to_string(keep_alive_timeout_sec_);
11022 s += ", max=";
11023 s += std::to_string(keep_alive_max_count_);
11024 res.set_header("Keep-Alive", s);
11025 }
11026
11027 if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) &&
11028 !res.has_header("Content-Type")) {
11029 res.set_header("Content-Type", "text/plain");
11030 }
11031
11032 if (res.body.empty() && !res.content_length_ && !res.content_provider_ &&
11033 !res.has_header("Content-Length")) {
11034 res.set_header("Content-Length", "0");
11035 }
11036
11037 if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) {
11038 res.set_header("Accept-Ranges", "bytes");
11039 }
11040
11041 if (post_routing_handler_) { post_routing_handler_(req, res); }
11042
11043 // Response line and headers
11044 detail::BufferStream bstrm;
11045 if (!detail::write_response_line(bstrm, res.status)) { return false; }
11046 if (header_writer_(bstrm, res.headers) <= 0) { return false; }
11047
11048 // Combine small body with headers to reduce write syscalls
11049 if (req.method != "HEAD" && !res.body.empty() && !res.content_provider_) {
11050 bstrm.write(res.body.data(), res.body.size());
11051 }
11052
11053 // Log before writing to avoid race condition with client-side code that
11054 // accesses logger-captured data immediately after receiving the response.
11055 output_log(req, res);
11056
11057 // Flush buffer
11058 auto &data = bstrm.get_buffer();
11059 if (!detail::write_data(strm, data.data(), data.size())) { return false; }
11060
11061 // Streaming body
11062 auto ret = true;
11063 if (req.method != "HEAD" && res.content_provider_) {
11064 if (write_content_with_provider(strm, req, res, boundary, content_type)) {
11065 res.content_provider_success_ = true;
11066 } else {
11067 ret = false;
11068 }
11069 }
11070
11071 return ret;
11072}
11073
11074inline bool
11075Server::write_content_with_provider(Stream &strm, const Request &req,
11076 Response &res, const std::string &boundary,
11077 const std::string &content_type) {
11078 auto is_shutting_down = [this]() {
11079 return this->svr_sock_ == INVALID_SOCKET;
11080 };
11081
11082 if (res.content_length_ > 0) {
11083 if (req.ranges.empty()) {
11084 return detail::write_content(strm, res.content_provider_, 0,
11085 res.content_length_, is_shutting_down);
11086 } else if (req.ranges.size() == 1) {
11087 auto offset_and_length = detail::get_range_offset_and_length(
11088 req.ranges[0], res.content_length_);
11089
11090 return detail::write_content(strm, res.content_provider_,
11091 offset_and_length.first,
11092 offset_and_length.second, is_shutting_down);
11093 } else {
11095 strm, req, res, boundary, content_type, res.content_length_,
11096 is_shutting_down);
11097 }
11098 } else {
11099 if (res.is_chunked_content_provider_) {
11100 auto type = detail::encoding_type(req, res);
11101
11102 std::unique_ptr<detail::compressor> compressor;
11103 if (type == detail::EncodingType::Gzip) {
11104#ifdef CPPHTTPLIB_ZLIB_SUPPORT
11106#endif
11107 } else if (type == detail::EncodingType::Brotli) {
11108#ifdef CPPHTTPLIB_BROTLI_SUPPORT
11110#endif
11111 } else if (type == detail::EncodingType::Zstd) {
11112#ifdef CPPHTTPLIB_ZSTD_SUPPORT
11114#endif
11115 } else {
11117 }
11118 assert(compressor != nullptr);
11119
11120 return detail::write_content_chunked(strm, res.content_provider_,
11121 is_shutting_down, *compressor);
11122 } else {
11123 return detail::write_content_without_length(strm, res.content_provider_,
11124 is_shutting_down);
11125 }
11126 }
11127}
11128
11129inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
11130 FormFields::iterator cur_field;
11131 FormFiles::iterator cur_file;
11132 auto is_text_field = false;
11133 size_t count = 0;
11134 if (read_content_core(
11135 strm, req, res,
11136 // Regular
11137 [&](const char *buf, size_t n) {
11138 // Prevent arithmetic overflow when checking sizes.
11139 // Avoid computing (req.body.size() + n) directly because
11140 // adding two unsigned `size_t` values can wrap around and
11141 // produce a small result instead of indicating overflow.
11142 // Instead, check using subtraction: ensure `n` does not
11143 // exceed the remaining capacity `max_size() - size()`.
11144 if (req.body.size() >= req.body.max_size() ||
11145 n > req.body.max_size() - req.body.size()) {
11146 return false;
11147 }
11148
11149 // Limit decompressed body size to payload_max_length_ to protect
11150 // against "zip bomb" attacks where a small compressed payload
11151 // decompresses to a massive size.
11152 if (payload_max_length_ > 0 &&
11153 (req.body.size() >= payload_max_length_ ||
11154 n > payload_max_length_ - req.body.size())) {
11155 return false;
11156 }
11157
11158 req.body.append(buf, n);
11159 return true;
11160 },
11161 // Multipart FormData
11162 [&](const FormData &file) {
11164 output_error_log(Error::TooManyFormDataFiles, &req);
11165 return false;
11166 }
11167
11168 if (file.filename.empty()) {
11169 cur_field = req.form.fields.emplace(
11170 file.name, FormField{file.name, file.content, file.headers});
11171 is_text_field = true;
11172 } else {
11173 cur_file = req.form.files.emplace(file.name, file);
11174 is_text_field = false;
11175 }
11176 return true;
11177 },
11178 [&](const char *buf, size_t n) {
11179 if (is_text_field) {
11180 auto &content = cur_field->second.content;
11181 if (content.size() + n > content.max_size()) { return false; }
11182 content.append(buf, n);
11183 } else {
11184 auto &content = cur_file->second.content;
11185 if (content.size() + n > content.max_size()) { return false; }
11186 content.append(buf, n);
11187 }
11188 return true;
11189 })) {
11190 const auto &content_type = req.get_header_value("Content-Type");
11191 if (detail::extract_media_type(content_type) ==
11192 "application/x-www-form-urlencoded") {
11193 if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
11194 res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
11195 output_error_log(Error::ExceedMaxPayloadSize, &req);
11196 return false;
11197 }
11198 detail::parse_query_text(req.body, req.params);
11199 }
11200 return true;
11201 }
11202 return false;
11203}
11204
11205inline bool Server::read_content_with_content_receiver(
11206 Stream &strm, Request &req, Response &res, ContentReceiver receiver,
11207 FormDataHeader multipart_header, ContentReceiver multipart_receiver) {
11208 return read_content_core(strm, req, res, std::move(receiver),
11209 std::move(multipart_header),
11210 std::move(multipart_receiver));
11211}
11212
11213inline bool Server::read_content_core(
11214 Stream &strm, Request &req, Response &res, ContentReceiver receiver,
11215 FormDataHeader multipart_header, ContentReceiver multipart_receiver) const {
11216 detail::FormDataParser multipart_form_data_parser;
11218
11219 if (req.is_multipart_form_data()) {
11220 const auto &content_type = req.get_header_value("Content-Type");
11221 std::string boundary;
11222 if (!detail::parse_multipart_boundary(content_type, boundary)) {
11223 res.status = StatusCode::BadRequest_400;
11224 output_error_log(Error::MultipartParsing, &req);
11225 return false;
11226 }
11227
11228 multipart_form_data_parser.set_boundary(std::move(boundary));
11229 out = [&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) {
11230 return multipart_form_data_parser.parse(buf, n, multipart_header,
11231 multipart_receiver);
11232 };
11233 } else {
11234 out = [receiver](const char *buf, size_t n, size_t /*off*/,
11235 size_t /*len*/) { return receiver(buf, n); };
11236 }
11237
11238 // RFC 7230 Section 3.3.3: If this is a request message and none of the above
11239 // are true (no Transfer-Encoding and no Content-Length), then the message
11240 // body length is zero (no message body is present).
11241 //
11242 // For non-SSL builds, detect clients that send a body without a
11243 // Content-Length header (raw HTTP over TCP). Check both the stream's
11244 // internal read buffer (data already read from the socket during header
11245 // parsing) and the socket itself for pending data. If data is found and
11246 // exceeds the configured payload limit, reject with 413.
11247 // For SSL builds we cannot reliably peek the decrypted application bytes,
11248 // so keep the original behaviour.
11249#if !defined(CPPHTTPLIB_SSL_ENABLED)
11250 if (!req.has_header("Content-Length") &&
11252 // Only check if payload_max_length is set to a finite value
11253 if (payload_max_length_ > 0 &&
11254 payload_max_length_ < (std::numeric_limits<size_t>::max)()) {
11255 // Check if there is data already buffered in the stream (read during
11256 // header parsing) or pending on the socket. Use a non-blocking socket
11257 // check to avoid deadlock when the client sends no body.
11258 bool has_data = strm.is_readable();
11259 if (!has_data) {
11260 socket_t s = strm.socket();
11261 if (s != INVALID_SOCKET) {
11262 has_data = detail::select_read(s, 0, 0) > 0;
11263 }
11264 }
11265 if (has_data) {
11266 auto result =
11270 return false;
11271 } else if (result != detail::ReadContentResult::Success) {
11272 return false;
11273 }
11274 return true;
11275 }
11276 }
11277 return true;
11278 }
11279#else
11280 if (!req.has_header("Content-Length") &&
11282 return true;
11283 }
11284#endif
11285
11286 if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
11287 out, true)) {
11288 return false;
11289 }
11290
11291 if (req.is_multipart_form_data()) {
11292 if (!multipart_form_data_parser.is_valid()) {
11293 res.status = StatusCode::BadRequest_400;
11294 output_error_log(Error::MultipartParsing, &req);
11295 return false;
11296 }
11297 }
11298
11299 return true;
11300}
11301
11302inline bool Server::handle_file_request(Request &req, Response &res) {
11303 for (const auto &entry : base_dirs_) {
11304 // Prefix match
11305 if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
11306 std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
11307 if (detail::is_valid_path(sub_path)) {
11308 auto path = entry.base_dir + sub_path;
11309 if (path.back() == '/') { path += "index.html"; }
11310
11311 // Defense-in-depth: is_valid_path blocks ".." traversal in the URL,
11312 // but symlinks/junctions can still escape the base directory.
11313 if (!entry.resolved_base_dir.empty()) {
11314 std::string resolved_path;
11315 if (detail::canonicalize_path(path.c_str(), resolved_path) &&
11316 !detail::is_path_within_base(resolved_path,
11317 entry.resolved_base_dir)) {
11318 res.status = StatusCode::Forbidden_403;
11319 return true;
11320 }
11321 }
11322
11323 detail::FileStat stat(path);
11324
11325 if (stat.is_dir()) {
11326 res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301);
11327 return true;
11328 }
11329
11330 if (stat.is_file()) {
11331 for (const auto &kv : entry.headers) {
11332 res.set_header(kv.first, kv.second);
11333 }
11334
11335 auto etag = detail::compute_etag(stat);
11336 if (!etag.empty()) { res.set_header("ETag", etag); }
11337
11338 auto mtime = stat.mtime();
11339
11340 auto last_modified = detail::file_mtime_to_http_date(mtime);
11341 if (!last_modified.empty()) {
11342 res.set_header("Last-Modified", last_modified);
11343 }
11344
11345 if (check_if_not_modified(req, res, etag, mtime)) { return true; }
11346
11347 check_if_range(req, etag, mtime);
11348
11349 auto mm = std::make_shared<detail::mmap>(path.c_str());
11350 if (!mm->is_open()) {
11351 output_error_log(Error::OpenFile, &req);
11352 return false;
11353 }
11354
11355 res.set_content_provider(
11356 mm->size(),
11357 detail::find_content_type(path, file_extension_and_mimetype_map_,
11358 default_file_mimetype_),
11359 [mm](size_t offset, size_t length, DataSink &sink) -> bool {
11360 sink.write(mm->data() + offset, length);
11361 return true;
11362 });
11363
11364 if (req.method != "HEAD" && file_request_handler_) {
11365 file_request_handler_(req, res);
11366 }
11367
11368 return true;
11369 } else {
11370 output_error_log(Error::OpenFile, &req);
11371 }
11372 }
11373 }
11374 }
11375 return false;
11376}
11377
11378inline bool Server::check_if_not_modified(const Request &req, Response &res,
11379 const std::string &etag,
11380 time_t mtime) const {
11381 // Handle conditional GET:
11382 // 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2)
11383 // 2. If-Modified-Since is checked only when If-None-Match is absent
11384 if (req.has_header("If-None-Match")) {
11385 if (!etag.empty()) {
11386 auto val = req.get_header_value("If-None-Match");
11387
11388 // NOTE: We use exact string matching here. This works correctly
11389 // because our server always generates weak ETags (W/"..."), and
11390 // clients typically send back the same ETag they received.
11391 // RFC 9110 Section 8.8.3.2 allows weak comparison for
11392 // If-None-Match, where W/"x" and "x" would match, but this
11393 // simplified implementation requires exact matches.
11394 auto ret = detail::split_find(val.data(), val.data() + val.size(), ',',
11395 [&](const char *b, const char *e) {
11396 auto seg_len = static_cast<size_t>(e - b);
11397 return (seg_len == 1 && *b == '*') ||
11398 (seg_len == etag.size() &&
11399 std::equal(b, e, etag.begin()));
11400 });
11401
11402 if (ret) {
11403 res.status = StatusCode::NotModified_304;
11404 return true;
11405 }
11406 }
11407 } else if (req.has_header("If-Modified-Since")) {
11408 auto val = req.get_header_value("If-Modified-Since");
11409 auto t = detail::parse_http_date(val);
11410
11411 if (t != static_cast<time_t>(-1) && mtime <= t) {
11412 res.status = StatusCode::NotModified_304;
11413 return true;
11414 }
11415 }
11416 return false;
11417}
11418
11419inline bool Server::check_if_range(Request &req, const std::string &etag,
11420 time_t mtime) const {
11421 // Handle If-Range for partial content requests (RFC 9110
11422 // Section 13.1.5). If-Range is only evaluated when Range header is
11423 // present. If the validator matches, serve partial content; otherwise
11424 // serve full content.
11425 if (!req.ranges.empty() && req.has_header("If-Range")) {
11426 auto val = req.get_header_value("If-Range");
11427
11428 auto is_valid_range = [&]() {
11429 if (detail::is_strong_etag(val)) {
11430 // RFC 9110 Section 13.1.5: If-Range requires strong ETag
11431 // comparison.
11432 return (!etag.empty() && val == etag);
11433 } else if (detail::is_weak_etag(val)) {
11434 // Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5)
11435 return false;
11436 } else {
11437 // HTTP-date comparison
11438 auto t = detail::parse_http_date(val);
11439 return (t != static_cast<time_t>(-1) && mtime <= t);
11440 }
11441 };
11442
11443 if (!is_valid_range()) {
11444 // Validator doesn't match: ignore Range and serve full content
11445 req.ranges.clear();
11446 return false;
11447 }
11448 }
11449
11450 return true;
11451}
11452
11453inline socket_t
11454Server::create_server_socket(const std::string &host, int port,
11455 int socket_flags,
11456 SocketOptions socket_options) const {
11457 return detail::create_socket(
11458 host, std::string(), port, address_family_, socket_flags, tcp_nodelay_,
11459 ipv6_v6only_, std::move(socket_options),
11460 [&](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
11461 if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
11462 output_error_log(Error::BindIPAddress, nullptr);
11463 return false;
11464 }
11466 output_error_log(Error::Listen, nullptr);
11467 return false;
11468 }
11469 return true;
11470 });
11471}
11472
11473inline int Server::bind_internal(const std::string &host, int port,
11474 int socket_flags) {
11475 if (is_decommissioned) { return -1; }
11476
11477 if (!is_valid()) { return -1; }
11478
11479 svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
11480 if (svr_sock_ == INVALID_SOCKET) { return -1; }
11481
11482 if (port == 0) {
11483 struct sockaddr_storage addr;
11484 socklen_t addr_len = sizeof(addr);
11485 if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
11486 &addr_len) == -1) {
11487 output_error_log(Error::GetSockName, nullptr);
11488 return -1;
11489 }
11490 if (addr.ss_family == AF_INET) {
11491 return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
11492 } else if (addr.ss_family == AF_INET6) {
11493 return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
11494 } else {
11495 output_error_log(Error::UnsupportedAddressFamily, nullptr);
11496 return -1;
11497 }
11498 } else {
11499 return port;
11500 }
11501}
11502
11503inline bool Server::listen_internal() {
11504 if (is_decommissioned) { return false; }
11505
11506 auto ret = true;
11507 is_running_ = true;
11508 auto se = detail::scope_exit([&]() { is_running_ = false; });
11509
11510 {
11511 std::unique_ptr<TaskQueue> task_queue(new_task_queue());
11512
11513 while (svr_sock_ != INVALID_SOCKET) {
11514#ifndef _WIN32
11515 if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
11516#endif
11519 if (val == 0) { // Timeout
11520 task_queue->on_idle();
11521 continue;
11522 }
11523#ifndef _WIN32
11524 }
11525#endif
11526
11527#if defined _WIN32
11528 // sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT,
11529 // OVERLAPPED
11530 socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0);
11531#elif defined SOCK_CLOEXEC
11532 socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC);
11533#else
11534 socket_t sock = accept(svr_sock_, nullptr, nullptr);
11535#endif
11536
11537 if (sock == INVALID_SOCKET) {
11538 if (errno == EMFILE) {
11539 // The per-process limit of open file descriptors has been reached.
11540 // Try to accept new connections after a short sleep.
11541 std::this_thread::sleep_for(std::chrono::microseconds{1});
11542 continue;
11543 } else if (errno == EINTR || errno == EAGAIN) {
11544 continue;
11545 }
11546 if (svr_sock_ != INVALID_SOCKET) {
11548 ret = false;
11549 output_error_log(Error::Connection, nullptr);
11550 } else {
11551 ; // The server socket was closed by user.
11552 }
11553 break;
11554 }
11555
11556 detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO,
11558 detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO,
11560
11561 if (tcp_nodelay_) {
11562 detail::set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1);
11563 }
11564
11565 if (!task_queue->enqueue(
11566 [this, sock]() { process_and_close_socket(sock); })) {
11567 output_error_log(Error::ResourceExhaustion, nullptr);
11570 }
11571 }
11572
11573 task_queue->shutdown();
11574 }
11575
11576 is_decommissioned = !ret;
11577 return ret;
11578}
11579
11580inline bool Server::routing(Request &req, Response &res, Stream &strm) {
11581 if (pre_routing_handler_ &&
11582 pre_routing_handler_(req, res) == HandlerResponse::Handled) {
11583 return true;
11584 }
11585
11586 // File handler
11587 if ((req.method == "GET" || req.method == "HEAD") &&
11588 handle_file_request(req, res)) {
11589 return true;
11590 }
11591
11592 if (detail::expect_content(req)) {
11593 // Content reader handler
11594 {
11595 // Track whether the ContentReader was aborted due to the decompressed
11596 // payload exceeding `payload_max_length_`.
11597 // The user handler runs after the lambda returns, so we must restore the
11598 // 413 status if the handler overwrites it.
11599 bool content_reader_payload_too_large = false;
11600
11601 ContentReader reader(
11602 [&](ContentReceiver receiver) {
11603 auto result = read_content_with_content_receiver(
11604 strm, req, res, std::move(receiver), nullptr, nullptr);
11605 if (!result) {
11606 output_error_log(Error::Read, &req);
11607 if (res.status == StatusCode::PayloadTooLarge_413) {
11608 content_reader_payload_too_large = true;
11609 }
11610 }
11611 return result;
11612 },
11613 [&](FormDataHeader header, ContentReceiver receiver) {
11614 auto result = read_content_with_content_receiver(
11615 strm, req, res, nullptr, std::move(header),
11616 std::move(receiver));
11617 if (!result) {
11618 output_error_log(Error::Read, &req);
11619 if (res.status == StatusCode::PayloadTooLarge_413) {
11620 content_reader_payload_too_large = true;
11621 }
11622 }
11623 return result;
11624 });
11625
11626 bool dispatched = false;
11627 if (req.method == "POST") {
11628 dispatched = dispatch_request_for_content_reader(
11629 req, res, std::move(reader), post_handlers_for_content_reader_);
11630 } else if (req.method == "PUT") {
11631 dispatched = dispatch_request_for_content_reader(
11632 req, res, std::move(reader), put_handlers_for_content_reader_);
11633 } else if (req.method == "PATCH") {
11634 dispatched = dispatch_request_for_content_reader(
11635 req, res, std::move(reader), patch_handlers_for_content_reader_);
11636 } else if (req.method == "DELETE") {
11637 dispatched = dispatch_request_for_content_reader(
11638 req, res, std::move(reader), delete_handlers_for_content_reader_);
11639 }
11640
11641 if (dispatched) {
11642 if (content_reader_payload_too_large) {
11643 // Enforce the limit: override any status the handler may have set
11644 // and return false so the error path sends a plain 413 response.
11646 res.body.clear();
11647 res.content_length_ = 0;
11648 res.content_provider_ = nullptr;
11649 return false;
11650 }
11651 return true;
11652 }
11653 }
11654
11655 // Read content into `req.body`
11656 if (!read_content(strm, req, res)) {
11657 output_error_log(Error::Read, &req);
11658 return false;
11659 }
11660 }
11661
11662 // Regular handler
11663 if (req.method == "GET" || req.method == "HEAD") {
11664 return dispatch_request(req, res, get_handlers_);
11665 } else if (req.method == "POST") {
11666 return dispatch_request(req, res, post_handlers_);
11667 } else if (req.method == "PUT") {
11668 return dispatch_request(req, res, put_handlers_);
11669 } else if (req.method == "DELETE") {
11670 return dispatch_request(req, res, delete_handlers_);
11671 } else if (req.method == "OPTIONS") {
11672 return dispatch_request(req, res, options_handlers_);
11673 } else if (req.method == "PATCH") {
11674 return dispatch_request(req, res, patch_handlers_);
11675 }
11676
11677 res.status = StatusCode::BadRequest_400;
11678 return false;
11679}
11680
11681inline bool Server::dispatch_request(Request &req, Response &res,
11682 const Handlers &handlers) const {
11683 for (const auto &x : handlers) {
11684 const auto &matcher = x.first;
11685 const auto &handler = x.second;
11686
11687 if (matcher->match(req)) {
11688 req.matched_route = matcher->pattern();
11689 if (!pre_request_handler_ ||
11690 pre_request_handler_(req, res) != HandlerResponse::Handled) {
11691 handler(req, res);
11692 }
11693 return true;
11694 }
11695 }
11696 return false;
11697}
11698
11699inline void Server::apply_ranges(const Request &req, Response &res,
11700 std::string &content_type,
11701 std::string &boundary) const {
11702 if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) {
11703 auto it = res.headers.find("Content-Type");
11704 if (it != res.headers.end()) {
11705 content_type = it->second;
11706 res.headers.erase(it);
11707 }
11708
11710
11711 res.set_header("Content-Type",
11712 "multipart/byteranges; boundary=" + boundary);
11713 }
11714
11715 auto type = detail::encoding_type(req, res);
11716
11717 if (res.body.empty()) {
11718 if (res.content_length_ > 0) {
11719 size_t length = 0;
11720 if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
11721 length = res.content_length_;
11722 } else if (req.ranges.size() == 1) {
11723 auto offset_and_length = detail::get_range_offset_and_length(
11724 req.ranges[0], res.content_length_);
11725
11726 length = offset_and_length.second;
11727
11728 auto content_range = detail::make_content_range_header_field(
11729 offset_and_length, res.content_length_);
11730 res.set_header("Content-Range", content_range);
11731 } else {
11733 req, boundary, content_type, res.content_length_);
11734 }
11735 res.set_header("Content-Length", std::to_string(length));
11736 } else {
11737 if (res.content_provider_) {
11738 if (res.is_chunked_content_provider_) {
11739 res.set_header("Transfer-Encoding", "chunked");
11740 if (type == detail::EncodingType::Gzip) {
11741 res.set_header("Content-Encoding", "gzip");
11742 res.set_header("Vary", "Accept-Encoding");
11743 } else if (type == detail::EncodingType::Brotli) {
11744 res.set_header("Content-Encoding", "br");
11745 res.set_header("Vary", "Accept-Encoding");
11746 } else if (type == detail::EncodingType::Zstd) {
11747 res.set_header("Content-Encoding", "zstd");
11748 res.set_header("Vary", "Accept-Encoding");
11749 }
11750 }
11751 }
11752 }
11753 } else {
11754 if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
11755 ;
11756 } else if (req.ranges.size() == 1) {
11757 auto offset_and_length =
11758 detail::get_range_offset_and_length(req.ranges[0], res.body.size());
11759 auto offset = offset_and_length.first;
11760 auto length = offset_and_length.second;
11761
11762 auto content_range = detail::make_content_range_header_field(
11763 offset_and_length, res.body.size());
11764 res.set_header("Content-Range", content_range);
11765
11766 assert(offset + length <= res.body.size());
11767 res.body = res.body.substr(offset, length);
11768 } else {
11769 std::string data;
11770 detail::make_multipart_ranges_data(req, res, boundary, content_type,
11771 res.body.size(), data);
11772 res.body.swap(data);
11773 }
11774
11775 if (type != detail::EncodingType::None) {
11776 output_pre_compression_log(req, res);
11777
11778 std::unique_ptr<detail::compressor> compressor;
11779 std::string content_encoding;
11780
11781 if (type == detail::EncodingType::Gzip) {
11782#ifdef CPPHTTPLIB_ZLIB_SUPPORT
11784 content_encoding = "gzip";
11785#endif
11786 } else if (type == detail::EncodingType::Brotli) {
11787#ifdef CPPHTTPLIB_BROTLI_SUPPORT
11789 content_encoding = "br";
11790#endif
11791 } else if (type == detail::EncodingType::Zstd) {
11792#ifdef CPPHTTPLIB_ZSTD_SUPPORT
11794 content_encoding = "zstd";
11795#endif
11796 }
11797
11798 if (compressor) {
11799 std::string compressed;
11800 if (compressor->compress(res.body.data(), res.body.size(), true,
11801 [&](const char *data, size_t data_len) {
11802 compressed.append(data, data_len);
11803 return true;
11804 })) {
11805 res.body.swap(compressed);
11806 res.set_header("Content-Encoding", content_encoding);
11807 res.set_header("Vary", "Accept-Encoding");
11808 }
11809 }
11810 }
11811
11812 auto length = std::to_string(res.body.size());
11813 res.set_header("Content-Length", length);
11814 }
11815}
11816
11817inline bool Server::dispatch_request_for_content_reader(
11818 Request &req, Response &res, ContentReader content_reader,
11819 const HandlersForContentReader &handlers) const {
11820 for (const auto &x : handlers) {
11821 const auto &matcher = x.first;
11822 const auto &handler = x.second;
11823
11824 if (matcher->match(req)) {
11825 req.matched_route = matcher->pattern();
11826 if (!pre_request_handler_ ||
11827 pre_request_handler_(req, res) != HandlerResponse::Handled) {
11828 handler(req, res, content_reader);
11829 }
11830 return true;
11831 }
11832 }
11833 return false;
11834}
11835
11836inline std::string
11837get_client_ip(const std::string &x_forwarded_for,
11838 const std::vector<std::string> &trusted_proxies) {
11839 // X-Forwarded-For is a comma-separated list per RFC 7239
11840 std::vector<std::string> ip_list;
11841 detail::split(x_forwarded_for.data(),
11842 x_forwarded_for.data() + x_forwarded_for.size(), ',',
11843 [&](const char *b, const char *e) {
11844 auto r = detail::trim(b, e, 0, static_cast<size_t>(e - b));
11845 ip_list.emplace_back(std::string(b + r.first, b + r.second));
11846 });
11847
11848 for (size_t i = 0; i < ip_list.size(); ++i) {
11849 auto ip = ip_list[i];
11850
11851 auto is_trusted_proxy =
11852 std::any_of(trusted_proxies.begin(), trusted_proxies.end(),
11853 [&](const std::string &proxy) { return ip == proxy; });
11854
11855 if (is_trusted_proxy) {
11856 if (i == 0) {
11857 // If the trusted proxy is the first IP, there's no preceding client IP
11858 return ip;
11859 } else {
11860 // Return the IP immediately before the trusted proxy
11861 return ip_list[i - 1];
11862 }
11863 }
11864 }
11865
11866 // If no trusted proxy is found, return the first IP in the list
11867 return ip_list.front();
11868}
11869
11870inline bool
11871Server::process_request(Stream &strm, const std::string &remote_addr,
11872 int remote_port, const std::string &local_addr,
11873 int local_port, bool close_connection,
11874 bool &connection_closed,
11875 const std::function<void(Request &)> &setup_request,
11876 bool *websocket_upgraded) {
11877 std::array<char, 2048> buf{};
11878
11879 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
11880
11881 // Connection has been closed on client
11882 if (!line_reader.getline()) { return false; }
11883
11884 Request req;
11885 req.start_time_ = std::chrono::steady_clock::now();
11886 req.remote_addr = remote_addr;
11887 req.remote_port = remote_port;
11888 req.local_addr = local_addr;
11889 req.local_port = local_port;
11890
11891 Response res;
11892 res.version = "HTTP/1.1";
11893 res.headers = default_headers_;
11894
11895 // Request line and headers
11896 if (!parse_request_line(line_reader.ptr(), req)) {
11898 output_error_log(Error::InvalidRequestLine, &req);
11899 return write_response(strm, close_connection, req, res);
11900 }
11901
11902 // Request headers
11903 if (!detail::read_headers(strm, req.headers)) {
11905 output_error_log(Error::InvalidHeaders, &req);
11906 return write_response(strm, close_connection, req, res);
11907 }
11908
11909 // Check if the request URI doesn't exceed the limit
11910 if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
11912 output_error_log(Error::ExceedUriMaxLength, &req);
11913 return write_response(strm, close_connection, req, res);
11914 }
11915
11916 if (req.get_header_value("Connection") == "close") {
11917 connection_closed = true;
11918 }
11919
11920 if (req.version == "HTTP/1.0" &&
11921 req.get_header_value("Connection") != "Keep-Alive") {
11922 connection_closed = true;
11923 }
11924
11925 if (!trusted_proxies_.empty() && req.has_header("X-Forwarded-For")) {
11926 auto x_forwarded_for = req.get_header_value("X-Forwarded-For");
11927 req.remote_addr = get_client_ip(x_forwarded_for, trusted_proxies_);
11928 } else {
11929 req.remote_addr = remote_addr;
11930 }
11931 req.remote_port = remote_port;
11932
11933 req.local_addr = local_addr;
11934 req.local_port = local_port;
11935
11936 if (req.has_header("Accept")) {
11937 const auto &accept_header = req.get_header_value("Accept");
11938 if (!detail::parse_accept_header(accept_header, req.accept_content_types)) {
11940 output_error_log(Error::HTTPParsing, &req);
11941 return write_response(strm, close_connection, req, res);
11942 }
11943 }
11944
11945 if (req.has_header("Range")) {
11946 const auto &range_header_value = req.get_header_value("Range");
11947 if (!detail::parse_range_header(range_header_value, req.ranges)) {
11949 output_error_log(Error::InvalidRangeHeader, &req);
11950 return write_response(strm, close_connection, req, res);
11951 }
11952 }
11953
11954 if (setup_request) { setup_request(req); }
11955
11956 if (req.get_header_value("Expect") == "100-continue") {
11957 int status = StatusCode::Continue_100;
11958 if (expect_100_continue_handler_) {
11959 status = expect_100_continue_handler_(req, res);
11960 }
11961 switch (status) {
11964 detail::write_response_line(strm, status);
11965 strm.write("\r\n");
11966 break;
11967 default:
11968 connection_closed = true;
11969 return write_response(strm, true, req, res);
11970 }
11971 }
11972
11973 // Setup `is_connection_closed` method
11974 auto sock = strm.socket();
11975 req.is_connection_closed = [sock]() {
11976 return !detail::is_socket_alive(sock);
11977 };
11978
11979 // WebSocket upgrade
11980 // Check pre_routing_handler_ before upgrading so that authentication
11981 // and other middleware can reject the request with an HTTP response
11982 // (e.g., 401) before the protocol switches.
11984 if (pre_routing_handler_ &&
11985 pre_routing_handler_(req, res) == HandlerResponse::Handled) {
11986 if (res.status == -1) { res.status = StatusCode::OK_200; }
11987 return write_response(strm, close_connection, req, res);
11988 }
11989 // Find matching WebSocket handler
11990 for (const auto &entry : websocket_handlers_) {
11991 if (entry.matcher->match(req)) {
11992 // Compute accept key
11993 auto client_key = req.get_header_value("Sec-WebSocket-Key");
11994 auto accept_key = detail::websocket_accept_key(client_key);
11995
11996 // Negotiate subprotocol
11997 std::string selected_subprotocol;
11998 if (entry.sub_protocol_selector) {
11999 auto protocol_header = req.get_header_value("Sec-WebSocket-Protocol");
12000 if (!protocol_header.empty()) {
12001 std::vector<std::string> protocols;
12002 std::istringstream iss(protocol_header);
12003 std::string token;
12004 while (std::getline(iss, token, ',')) {
12005 // Trim whitespace
12006 auto start = token.find_first_not_of(' ');
12007 auto end = token.find_last_not_of(' ');
12008 if (start != std::string::npos) {
12009 protocols.push_back(token.substr(start, end - start + 1));
12010 }
12011 }
12012 selected_subprotocol = entry.sub_protocol_selector(protocols);
12013 }
12014 }
12015
12016 // Send 101 Switching Protocols
12017 std::string handshake_response = "HTTP/1.1 101 Switching Protocols\r\n"
12018 "Upgrade: websocket\r\n"
12019 "Connection: Upgrade\r\n"
12020 "Sec-WebSocket-Accept: " +
12021 accept_key + "\r\n";
12022 if (!selected_subprotocol.empty()) {
12023 if (!detail::fields::is_field_value(selected_subprotocol)) {
12024 return false;
12025 }
12026 handshake_response +=
12027 "Sec-WebSocket-Protocol: " + selected_subprotocol + "\r\n";
12028 }
12029 handshake_response += "\r\n";
12030 if (strm.write(handshake_response.data(), handshake_response.size()) <
12031 0) {
12032 return false;
12033 }
12034
12035 connection_closed = true;
12036 if (websocket_upgraded) { *websocket_upgraded = true; }
12037
12038 {
12039 // Use WebSocket-specific read timeout instead of HTTP timeout
12042 entry.handler(req, ws);
12043 }
12044 return true;
12045 }
12046 }
12047 // No matching handler - fall through to 404
12048 }
12049
12050 // Routing
12051 auto routed = false;
12052#ifdef CPPHTTPLIB_NO_EXCEPTIONS
12053 routed = routing(req, res, strm);
12054#else
12055 try {
12056 routed = routing(req, res, strm);
12057 } catch (std::exception &) {
12058 if (exception_handler_) {
12059 auto ep = std::current_exception();
12060 exception_handler_(req, res, ep);
12061 routed = true;
12062 } else {
12064 }
12065 } catch (...) {
12066 if (exception_handler_) {
12067 auto ep = std::current_exception();
12068 exception_handler_(req, res, ep);
12069 routed = true;
12070 } else {
12072 }
12073 }
12074#endif
12075 if (routed) {
12076 if (res.status == -1) {
12077 res.status = req.ranges.empty() ? StatusCode::OK_200
12079 }
12080
12081 // Serve file content by using a content provider
12082 if (!res.file_content_path_.empty()) {
12083 const auto &path = res.file_content_path_;
12084 auto mm = std::make_shared<detail::mmap>(path.c_str());
12085 if (!mm->is_open()) {
12086 res.body.clear();
12087 res.content_length_ = 0;
12088 res.content_provider_ = nullptr;
12090 output_error_log(Error::OpenFile, &req);
12091 return write_response(strm, close_connection, req, res);
12092 }
12093
12094 auto content_type = res.file_content_content_type_;
12095 if (content_type.empty()) {
12096 content_type = detail::find_content_type(
12097 path, file_extension_and_mimetype_map_, default_file_mimetype_);
12098 }
12099
12101 mm->size(), content_type,
12102 [mm](size_t offset, size_t length, DataSink &sink) -> bool {
12103 sink.write(mm->data() + offset, length);
12104 return true;
12105 });
12106 }
12107
12108 if (detail::range_error(req, res)) {
12109 res.body.clear();
12110 res.content_length_ = 0;
12111 res.content_provider_ = nullptr;
12113 return write_response(strm, close_connection, req, res);
12114 }
12115
12116 return write_response_with_content(strm, close_connection, req, res);
12117 } else {
12118 if (res.status == -1) { res.status = StatusCode::NotFound_404; }
12119
12120 return write_response(strm, close_connection, req, res);
12121 }
12122}
12123
12124inline bool Server::is_valid() const { return true; }
12125
12126inline bool Server::process_and_close_socket(socket_t sock) {
12127 std::string remote_addr;
12128 int remote_port = 0;
12129 detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
12130
12131 std::string local_addr;
12132 int local_port = 0;
12133 detail::get_local_ip_and_port(sock, local_addr, local_port);
12134
12135 bool websocket_upgraded = false;
12140 [&](Stream &strm, bool close_connection, bool &connection_closed) {
12141 return process_request(strm, remote_addr, remote_port, local_addr,
12142 local_port, close_connection, connection_closed,
12143 nullptr, &websocket_upgraded);
12144 });
12145
12148 return ret;
12149}
12150
12151inline void Server::output_log(const Request &req, const Response &res) const {
12152 if (logger_) {
12153 std::lock_guard<std::mutex> guard(logger_mutex_);
12154 logger_(req, res);
12155 }
12156}
12157
12158inline void Server::output_pre_compression_log(const Request &req,
12159 const Response &res) const {
12160 if (pre_compression_logger_) {
12161 std::lock_guard<std::mutex> guard(logger_mutex_);
12162 pre_compression_logger_(req, res);
12163 }
12164}
12165
12166inline void Server::output_error_log(const Error &err,
12167 const Request *req) const {
12168 if (error_logger_) {
12169 std::lock_guard<std::mutex> guard(logger_mutex_);
12170 error_logger_(err, req);
12171 }
12172}
12173
12174/*
12175 * Group 5: ClientImpl and Client (Universal) implementation
12176 */
12177// HTTP client implementation
12178inline ClientImpl::ClientImpl(const std::string &host)
12179 : ClientImpl(host, 80, std::string(), std::string()) {}
12180
12181inline ClientImpl::ClientImpl(const std::string &host, int port)
12182 : ClientImpl(host, port, std::string(), std::string()) {}
12183
12184inline ClientImpl::ClientImpl(const std::string &host, int port,
12185 const std::string &client_cert_path,
12186 const std::string &client_key_path)
12187 : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
12188 client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
12189
12191 // Wait until all the requests in flight are handled.
12192 size_t retry_count = 10;
12193 while (retry_count-- > 0) {
12194 {
12195 std::lock_guard<std::mutex> guard(socket_mutex_);
12196 if (socket_requests_in_flight_ == 0) { break; }
12197 }
12198 std::this_thread::sleep_for(std::chrono::milliseconds{1});
12199 }
12200
12201 std::lock_guard<std::mutex> guard(socket_mutex_);
12204}
12205
12206inline bool ClientImpl::is_valid() const { return true; }
12207
12208inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
12227 compress_ = rhs.compress_;
12231 interface_ = rhs.interface_;
12237 logger_ = rhs.logger_;
12239
12240#ifdef CPPHTTPLIB_SSL_ENABLED
12241 digest_auth_username_ = rhs.digest_auth_username_;
12242 digest_auth_password_ = rhs.digest_auth_password_;
12243 proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
12244 proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
12245 ca_cert_file_path_ = rhs.ca_cert_file_path_;
12246 ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
12247 server_certificate_verification_ = rhs.server_certificate_verification_;
12248 server_hostname_verification_ = rhs.server_hostname_verification_;
12249#endif
12250}
12251
12252inline socket_t ClientImpl::create_client_socket(Error &error) const {
12253 if (!proxy_host_.empty() && proxy_port_ != -1) {
12259 }
12260
12261 // Check is custom IP specified for host_
12262 std::string ip;
12263 auto it = addr_map_.find(host_);
12264 if (it != addr_map_.end()) { ip = it->second; }
12265
12271}
12272
12274 Error &error) {
12275 auto sock = create_client_socket(error);
12276 if (sock == INVALID_SOCKET) { return false; }
12277 socket.sock = sock;
12278 return true;
12279}
12280
12282 return create_and_connect_socket(socket, error);
12283}
12284
12286 Socket & /*socket*/,
12287 std::chrono::time_point<std::chrono::steady_clock> /*start_time*/,
12288 Response & /*res*/, bool & /*success*/, Error & /*error*/) {
12289 return true;
12290}
12291
12292inline void ClientImpl::shutdown_ssl(Socket & /*socket*/,
12293 bool /*shutdown_gracefully*/) {
12294 // If there are any requests in flight from threads other than us, then it's
12295 // a thread-unsafe race because individual ssl* objects are not thread-safe.
12296 assert(socket_requests_in_flight_ == 0 ||
12297 socket_requests_are_from_thread_ == std::this_thread::get_id());
12298}
12299
12301 if (socket.sock == INVALID_SOCKET) { return; }
12303}
12304
12306 // If there are requests in flight in another thread, usually closing
12307 // the socket will be fine and they will simply receive an error when
12308 // using the closed socket, but it is still a bug since rarely the OS
12309 // may reassign the socket id to be used for a new socket, and then
12310 // suddenly they will be operating on a live socket that is different
12311 // than the one they intended!
12312 assert(socket_requests_in_flight_ == 0 ||
12313 socket_requests_are_from_thread_ == std::this_thread::get_id());
12314
12315 // It is also a bug if this happens while SSL is still active
12316#ifdef CPPHTTPLIB_SSL_ENABLED
12317 assert(socket.ssl == nullptr);
12318#endif
12319
12320 if (socket.sock == INVALID_SOCKET) { return; }
12322 socket.sock = INVALID_SOCKET;
12323}
12324
12325inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
12326 Response &res,
12327 bool skip_100_continue) const {
12328 std::array<char, 2048> buf{};
12329
12330 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
12331
12332 if (!line_reader.getline()) { return false; }
12333
12334#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
12335 thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
12336#else
12337 thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
12338#endif
12339
12340 std::cmatch m;
12341 if (!std::regex_match(line_reader.ptr(), m, re)) {
12342 return req.method == "CONNECT";
12343 }
12344 res.version = std::string(m[1]);
12345 res.status = std::stoi(std::string(m[2]));
12346 res.reason = std::string(m[3]);
12347
12348 // Ignore '100 Continue' (only when not using Expect: 100-continue explicitly)
12349 while (skip_100_continue && res.status == StatusCode::Continue_100) {
12350 if (!line_reader.getline()) { return false; } // CRLF
12351 if (!line_reader.getline()) { return false; } // next response line
12352
12353 if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
12354 res.version = std::string(m[1]);
12355 res.status = std::stoi(std::string(m[2]));
12356 res.reason = std::string(m[3]);
12357 }
12358
12359 return true;
12360}
12361
12362inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
12363 std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
12364 auto ret = send_(req, res, error);
12365 if (error == Error::SSLPeerCouldBeClosed_) {
12366 assert(!ret);
12367 ret = send_(req, res, error);
12368 // If still failing with SSLPeerCouldBeClosed_, convert to Read error
12369 if (error == Error::SSLPeerCouldBeClosed_) { error = Error::Read; }
12370 }
12371 return ret;
12372}
12373
12374inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
12375 {
12376 std::lock_guard<std::mutex> guard(socket_mutex_);
12377
12378 // Set this to false immediately - if it ever gets set to true by the end
12379 // of the request, we know another thread instructed us to close the
12380 // socket.
12382
12383 auto is_alive = false;
12384 if (socket_.is_open()) {
12386
12387#ifdef CPPHTTPLIB_SSL_ENABLED
12388 if (is_alive && is_ssl()) {
12389 if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
12390 is_alive = false;
12391 }
12392 }
12393#endif
12394
12395 if (!is_alive) {
12396 // Attempt to avoid sigpipe by shutting down non-gracefully if it
12397 // seems like the other side has already closed the connection Also,
12398 // there cannot be any requests in flight from other threads since we
12399 // locked request_mutex_, so safe to close everything immediately
12400 const bool shutdown_gracefully = false;
12401 shutdown_ssl(socket_, shutdown_gracefully);
12404 }
12405 }
12406
12407 if (!is_alive) {
12408 if (!ensure_socket_connection(socket_, error)) {
12409 output_error_log(error, &req);
12410 return false;
12411 }
12412
12413 {
12414 auto success = true;
12415 if (!setup_proxy_connection(socket_, req.start_time_, res, success,
12416 error)) {
12417 if (!success) { output_error_log(error, &req); }
12418 return success;
12419 }
12420 }
12421 }
12422
12423 // Mark the current socket as being in use so that it cannot be closed by
12424 // anyone else while this request is ongoing, even though we will be
12425 // releasing the mutex.
12427 assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
12428 }
12430 socket_requests_are_from_thread_ = std::this_thread::get_id();
12431 }
12432
12433 for (const auto &header : default_headers_) {
12434 if (req.headers.find(header.first) == req.headers.end()) {
12435 req.headers.insert(header);
12436 }
12437 }
12438
12439 auto ret = false;
12440 auto close_connection = !keep_alive_;
12441
12442 auto se = detail::scope_exit([&]() {
12443 // Briefly lock mutex in order to mark that a request is no longer ongoing
12444 std::lock_guard<std::mutex> guard(socket_mutex_);
12446 if (socket_requests_in_flight_ <= 0) {
12447 assert(socket_requests_in_flight_ == 0);
12448 socket_requests_are_from_thread_ = std::thread::id();
12449 }
12450
12451 if (socket_should_be_closed_when_request_is_done_ || close_connection ||
12452 !ret) {
12453 shutdown_ssl(socket_, true);
12456 }
12457 });
12458
12459 ret = process_socket(socket_, req.start_time_, [&](Stream &strm) {
12460 return handle_request(strm, req, res, close_connection, error);
12461 });
12462
12463 if (!ret) {
12464 if (error == Error::Success) {
12465 error = Error::Unknown;
12466 output_error_log(error, &req);
12467 }
12468 }
12469
12470 return ret;
12471}
12472
12473inline Result ClientImpl::send(const Request &req) {
12474 auto req2 = req;
12475 return send_(std::move(req2));
12476}
12477
12478inline Result ClientImpl::send_(Request &&req) {
12479 auto res = detail::make_unique<Response>();
12480 auto error = Error::Success;
12481 auto ret = send(req, *res, error);
12482#ifdef CPPHTTPLIB_SSL_ENABLED
12483 return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers),
12484 last_ssl_error_, last_backend_error_};
12485#else
12486 return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
12487#endif
12488}
12489
12490inline void ClientImpl::prepare_default_headers(Request &r, bool for_stream,
12491 const std::string &ct) {
12492 (void)for_stream;
12493 for (const auto &header : default_headers_) {
12494 if (!r.has_header(header.first)) { r.headers.insert(header); }
12495 }
12496
12497 if (!r.has_header("Host")) {
12498 if (address_family_ == AF_UNIX) {
12499 r.headers.emplace("Host", "localhost");
12500 } else {
12501 r.headers.emplace(
12502 "Host", detail::make_host_and_port_string(host_, port_, is_ssl()));
12503 }
12504 }
12505
12506 if (!r.has_header("Accept")) { r.headers.emplace("Accept", "*/*"); }
12507
12508 if (!r.content_receiver) {
12509 if (!r.has_header("Accept-Encoding")) {
12510 std::string accept_encoding;
12511#ifdef CPPHTTPLIB_BROTLI_SUPPORT
12512 accept_encoding = "br";
12513#endif
12514#ifdef CPPHTTPLIB_ZLIB_SUPPORT
12515 if (!accept_encoding.empty()) { accept_encoding += ", "; }
12516 accept_encoding += "gzip, deflate";
12517#endif
12518#ifdef CPPHTTPLIB_ZSTD_SUPPORT
12519 if (!accept_encoding.empty()) { accept_encoding += ", "; }
12520 accept_encoding += "zstd";
12521#endif
12522 r.set_header("Accept-Encoding", accept_encoding);
12523 }
12524
12525#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
12526 if (!r.has_header("User-Agent")) {
12527 auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
12528 r.set_header("User-Agent", agent);
12529 }
12530#endif
12531 }
12532
12533 if (!r.body.empty()) {
12534 if (!ct.empty() && !r.has_header("Content-Type")) {
12535 r.headers.emplace("Content-Type", ct);
12536 }
12537 if (!r.has_header("Content-Length")) {
12538 r.headers.emplace("Content-Length", std::to_string(r.body.size()));
12539 }
12540 }
12541}
12542
12543inline ClientImpl::StreamHandle
12544ClientImpl::open_stream(const std::string &method, const std::string &path,
12545 const Params &params, const Headers &headers,
12546 const std::string &body,
12547 const std::string &content_type) {
12548 StreamHandle handle;
12550 handle.error = Error::Success;
12551
12552 auto query_path = params.empty() ? path : append_query_params(path, params);
12553 handle.connection_ = detail::make_unique<ClientConnection>();
12554
12555 {
12556 std::lock_guard<std::mutex> guard(socket_mutex_);
12557
12558 auto is_alive = false;
12559 if (socket_.is_open()) {
12560 is_alive = detail::is_socket_alive(socket_.sock);
12561#ifdef CPPHTTPLIB_SSL_ENABLED
12562 if (is_alive && is_ssl()) {
12563 if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
12564 is_alive = false;
12565 }
12566 }
12567#endif
12568 if (!is_alive) {
12569 shutdown_ssl(socket_, false);
12572 }
12573 }
12574
12575 if (!is_alive) {
12576 if (!ensure_socket_connection(socket_, handle.error)) {
12577 handle.response.reset();
12578 return handle;
12579 }
12580
12581 {
12582 auto success = true;
12583 auto start_time = std::chrono::steady_clock::now();
12584 if (!setup_proxy_connection(socket_, start_time, *handle.response,
12585 success, handle.error)) {
12586 if (!success) { handle.response.reset(); }
12587 return handle;
12588 }
12589 }
12590 }
12591
12592 transfer_socket_ownership_to_handle(handle);
12593 }
12594
12595#ifdef CPPHTTPLIB_SSL_ENABLED
12596 if (is_ssl() && handle.connection_->session) {
12597 handle.socket_stream_ = detail::make_unique<detail::SSLSocketStream>(
12598 handle.connection_->sock, handle.connection_->session,
12601 } else {
12602 handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
12603 handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
12605 }
12606#else
12607 handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
12608 handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
12610#endif
12611 handle.stream_ = handle.socket_stream_.get();
12612
12613 Request req;
12614 req.method = method;
12615 req.path = query_path;
12616 req.headers = headers;
12617 req.body = body;
12618
12619 prepare_default_headers(req, true, content_type);
12620
12621 auto &strm = *handle.stream_;
12622 if (detail::write_request_line(strm, req.method, req.path) < 0) {
12623 handle.error = Error::Write;
12624 handle.response.reset();
12625 return handle;
12626 }
12627
12629 handle.error)) {
12630 handle.response.reset();
12631 return handle;
12632 }
12633
12634 if (!body.empty()) {
12635 if (strm.write(body.data(), body.size()) < 0) {
12636 handle.error = Error::Write;
12637 handle.response.reset();
12638 return handle;
12639 }
12640 }
12641
12642 if (!read_response_line(strm, req, *handle.response) ||
12643 !detail::read_headers(strm, handle.response->headers)) {
12644 handle.error = Error::Read;
12645 handle.response.reset();
12646 return handle;
12647 }
12648
12649 handle.body_reader_.stream = handle.stream_;
12650 handle.body_reader_.payload_max_length = payload_max_length_;
12651
12652 if (handle.response->has_header("Content-Length")) {
12653 bool is_invalid = false;
12654 auto content_length = detail::get_header_value_u64(
12655 handle.response->headers, "Content-Length", 0, 0, is_invalid);
12656 if (is_invalid) {
12657 handle.error = Error::Read;
12658 handle.response.reset();
12659 return handle;
12660 }
12661 handle.body_reader_.has_content_length = true;
12662 handle.body_reader_.content_length = content_length;
12663 }
12664
12665 auto transfer_encoding =
12666 handle.response->get_header_value("Transfer-Encoding");
12667 handle.body_reader_.chunked = (transfer_encoding == "chunked");
12668
12669 auto content_encoding = handle.response->get_header_value("Content-Encoding");
12670 if (!content_encoding.empty()) {
12671 handle.decompressor_ = detail::create_decompressor(content_encoding);
12672 }
12673
12674 return handle;
12675}
12676
12677inline ssize_t ClientImpl::StreamHandle::read(char *buf, size_t len) {
12678 if (!is_valid() || !response) { return -1; }
12679
12680 if (decompressor_) { return read_with_decompression(buf, len); }
12681 auto n = detail::read_body_content(stream_, body_reader_, buf, len);
12682
12683 if (n <= 0 && body_reader_.chunked && !trailers_parsed_ && stream_) {
12684 trailers_parsed_ = true;
12685 if (body_reader_.chunked_decoder) {
12686 if (!body_reader_.chunked_decoder->parse_trailers_into(
12687 response->trailers, response->headers)) {
12688 return n;
12689 }
12690 } else {
12691 detail::ChunkedDecoder dec(*stream_);
12692 if (!dec.parse_trailers_into(response->trailers, response->headers)) {
12693 return n;
12694 }
12695 }
12696 }
12697
12698 return n;
12699}
12700
12701inline ssize_t ClientImpl::StreamHandle::read_with_decompression(char *buf,
12702 size_t len) {
12703 if (decompress_offset_ < decompress_buffer_.size()) {
12704 auto available = decompress_buffer_.size() - decompress_offset_;
12705 auto to_copy = (std::min)(len, available);
12706 std::memcpy(buf, decompress_buffer_.data() + decompress_offset_, to_copy);
12707 decompress_offset_ += to_copy;
12708 decompressed_bytes_read_ += to_copy;
12709 return static_cast<ssize_t>(to_copy);
12710 }
12711
12712 decompress_buffer_.clear();
12713 decompress_offset_ = 0;
12714
12715 constexpr size_t kDecompressionBufferSize = 8192;
12716 char compressed_buf[kDecompressionBufferSize];
12717
12718 while (true) {
12719 auto n = detail::read_body_content(stream_, body_reader_, compressed_buf,
12720 sizeof(compressed_buf));
12721
12722 if (n <= 0) { return n; }
12723
12724 bool decompress_ok = decompressor_->decompress(
12725 compressed_buf, static_cast<size_t>(n),
12726 [this](const char *data, size_t data_len) {
12727 decompress_buffer_.append(data, data_len);
12728 auto limit = body_reader_.payload_max_length;
12729 if (decompressed_bytes_read_ + decompress_buffer_.size() > limit) {
12730 return false;
12731 }
12732 return true;
12733 });
12734
12735 if (!decompress_ok) {
12736 body_reader_.last_error = Error::Read;
12737 return -1;
12738 }
12739
12740 if (!decompress_buffer_.empty()) { break; }
12741 }
12742
12743 auto to_copy = (std::min)(len, decompress_buffer_.size());
12744 std::memcpy(buf, decompress_buffer_.data(), to_copy);
12745 decompress_offset_ = to_copy;
12746 decompressed_bytes_read_ += to_copy;
12747 return static_cast<ssize_t>(to_copy);
12748}
12749
12751 if (!response || !stream_ || !body_reader_.chunked || trailers_parsed_) {
12752 return;
12753 }
12754
12755 trailers_parsed_ = true;
12756
12757 const auto bufsiz = 128;
12758 char line_buf[bufsiz];
12759 detail::stream_line_reader line_reader(*stream_, line_buf, bufsiz);
12760
12761 if (!line_reader.getline()) { return; }
12762
12763 if (!detail::parse_trailers(line_reader, response->trailers,
12764 response->headers)) {
12765 return;
12766 }
12767}
12768
12769namespace detail {
12770
12772
12773inline ssize_t ChunkedDecoder::read_payload(char *buf, size_t len,
12774 size_t &out_chunk_offset,
12775 size_t &out_chunk_total) {
12776 if (finished) { return 0; }
12777
12778 if (chunk_remaining == 0) {
12780 if (!lr.getline()) { return -1; }
12781
12782 char *endptr = nullptr;
12783 unsigned long chunk_len = std::strtoul(lr.ptr(), &endptr, 16);
12784 if (endptr == lr.ptr()) { return -1; }
12785 if (chunk_len == ULONG_MAX) { return -1; }
12786
12787 if (chunk_len == 0) {
12788 chunk_remaining = 0;
12789 finished = true;
12790 out_chunk_offset = 0;
12791 out_chunk_total = 0;
12792 return 0;
12793 }
12794
12795 chunk_remaining = static_cast<size_t>(chunk_len);
12798 }
12799
12800 auto to_read = (std::min)(chunk_remaining, len);
12801 auto n = strm.read(buf, to_read);
12802 if (n <= 0) { return -1; }
12803
12804 auto offset_before = last_chunk_offset;
12805 last_chunk_offset += static_cast<size_t>(n);
12806 chunk_remaining -= static_cast<size_t>(n);
12807
12808 out_chunk_offset = offset_before;
12809 out_chunk_total = last_chunk_total;
12810
12811 if (chunk_remaining == 0) {
12813 if (!lr.getline()) { return -1; }
12814 if (std::strcmp(lr.ptr(), "\r\n") != 0) { return -1; }
12815 }
12816
12817 return n;
12818}
12819
12821 const Headers &src_headers) {
12823 if (!lr.getline()) { return false; }
12824 return parse_trailers(lr, dest, src_headers);
12825}
12826
12827} // namespace detail
12828
12829inline void
12830ClientImpl::transfer_socket_ownership_to_handle(StreamHandle &handle) {
12831 handle.connection_->sock = socket_.sock;
12832#ifdef CPPHTTPLIB_SSL_ENABLED
12833 handle.connection_->session = socket_.ssl;
12834 socket_.ssl = nullptr;
12835#endif
12837}
12838
12839inline bool ClientImpl::handle_request(Stream &strm, Request &req,
12840 Response &res, bool close_connection,
12841 Error &error) {
12842 if (req.path.empty()) {
12843 error = Error::Connection;
12844 output_error_log(error, &req);
12845 return false;
12846 }
12847
12848 auto req_save = req;
12849
12850 bool ret;
12851
12852 if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) {
12853 auto req2 = req;
12854 req2.path = "http://" +
12856 req.path;
12857 ret = process_request(strm, req2, res, close_connection, error);
12858 req = std::move(req2);
12859 req.path = req_save.path;
12860 } else {
12861 ret = process_request(strm, req, res, close_connection, error);
12862 }
12863
12864 if (!ret) { return false; }
12865
12866 if (res.get_header_value("Connection") == "close" ||
12867 (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
12868 // NOTE: this requires a not-entirely-obvious chain of calls to be correct
12869 // for this to be safe.
12870
12871 // This is safe to call because handle_request is only called by send_
12872 // which locks the request mutex during the process. It would be a bug
12873 // to call it from a different thread since it's a thread-safety issue
12874 // to do these things to the socket if another thread is using the socket.
12875 std::lock_guard<std::mutex> guard(socket_mutex_);
12876 shutdown_ssl(socket_, true);
12879 }
12880
12881 if (300 < res.status && res.status < 400 && follow_location_) {
12882 req = std::move(req_save);
12883 ret = redirect(req, res, error);
12884 }
12885
12886#ifdef CPPHTTPLIB_SSL_ENABLED
12887 if ((res.status == StatusCode::Unauthorized_401 ||
12889 req.authorization_count_ < 5) {
12890 auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
12891 const auto &username =
12892 is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
12893 const auto &password =
12894 is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
12895
12896 if (!username.empty() && !password.empty()) {
12897 std::map<std::string, std::string> auth;
12898 if (detail::parse_www_authenticate(res, auth, is_proxy)) {
12899 Request new_req = req;
12900 new_req.authorization_count_ += 1;
12901 new_req.headers.erase(is_proxy ? "Proxy-Authorization"
12902 : "Authorization");
12903 new_req.headers.insert(detail::make_digest_authentication_header(
12904 req, auth, new_req.authorization_count_, detail::random_string(10),
12905 username, password, is_proxy));
12906
12907 Response new_res;
12908
12909 ret = send(new_req, new_res, error);
12910 if (ret) { res = std::move(new_res); }
12911 }
12912 }
12913 }
12914#endif
12915
12916 return ret;
12917}
12918
12919inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
12920 if (req.redirect_count_ == 0) {
12922 output_error_log(error, &req);
12923 return false;
12924 }
12925
12926 auto location = res.get_header_value("location");
12927 if (location.empty()) { return false; }
12928
12929 thread_local const std::regex re(
12930 R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
12931
12932 std::smatch m;
12933 if (!std::regex_match(location, m, re)) { return false; }
12934
12935 auto scheme = is_ssl() ? "https" : "http";
12936
12937 auto next_scheme = m[1].str();
12938 auto next_host = m[2].str();
12939 if (next_host.empty()) { next_host = m[3].str(); }
12940 auto port_str = m[4].str();
12941 auto next_path = m[5].str();
12942 auto next_query = m[6].str();
12943
12944 auto next_port = port_;
12945 if (!port_str.empty()) {
12946 if (!detail::parse_port(port_str, next_port)) { return false; }
12947 } else if (!next_scheme.empty()) {
12948 next_port = next_scheme == "https" ? 443 : 80;
12949 }
12950
12951 if (next_scheme.empty()) { next_scheme = scheme; }
12952 if (next_host.empty()) { next_host = host_; }
12953 if (next_path.empty()) { next_path = "/"; }
12954
12955 auto path = decode_query_component(next_path, true) + next_query;
12956
12957 // Same host redirect - use current client
12958 if (next_scheme == scheme && next_host == host_ && next_port == port_) {
12959 return detail::redirect(*this, req, res, path, location, error);
12960 }
12961
12962 // Cross-host/scheme redirect - create new client with robust setup
12963 return create_redirect_client(next_scheme, next_host, next_port, req, res,
12964 path, location, error);
12965}
12966
12967// New method for robust redirect client creation
12968inline bool ClientImpl::create_redirect_client(
12969 const std::string &scheme, const std::string &host, int port, Request &req,
12970 Response &res, const std::string &path, const std::string &location,
12971 Error &error) {
12972 // Determine if we need SSL
12973 auto need_ssl = (scheme == "https");
12974
12975 // Clean up request headers that are host/client specific
12976 // Remove headers that should not be carried over to new host
12977 auto headers_to_remove =
12978 std::vector<std::string>{"Host", "Proxy-Authorization", "Authorization"};
12979
12980 for (const auto &header_name : headers_to_remove) {
12981 auto it = req.headers.find(header_name);
12982 while (it != req.headers.end()) {
12983 it = req.headers.erase(it);
12984 it = req.headers.find(header_name);
12985 }
12986 }
12987
12988 // Create appropriate client type and handle redirect
12989 if (need_ssl) {
12990#ifdef CPPHTTPLIB_SSL_ENABLED
12991 // Create SSL client for HTTPS redirect
12992 SSLClient redirect_client(host, port);
12993
12994 // Setup basic client configuration first
12995 setup_redirect_client(redirect_client);
12996
12997 redirect_client.enable_server_certificate_verification(
12998 server_certificate_verification_);
12999 redirect_client.enable_server_hostname_verification(
13000 server_hostname_verification_);
13001
13002 // Transfer CA certificate to redirect client
13003 if (!ca_cert_pem_.empty()) {
13004 redirect_client.load_ca_cert_store(ca_cert_pem_.c_str(),
13005 ca_cert_pem_.size());
13006 }
13007 if (!ca_cert_file_path_.empty()) {
13008 redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_);
13009 }
13010
13011 // Client certificates are set through constructor for SSLClient
13012 // NOTE: SSLClient constructor already takes client_cert_path and
13013 // client_key_path so we need to create it properly if client certs are
13014 // needed
13015
13016 // Execute the redirect
13017 return detail::redirect(redirect_client, req, res, path, location, error);
13018#else
13019 // SSL not supported - set appropriate error
13020 error = Error::SSLConnection;
13021 output_error_log(error, &req);
13022 return false;
13023#endif
13024 } else {
13025 // HTTP redirect
13026 ClientImpl redirect_client(host, port);
13027
13028 // Setup client with robust configuration
13029 setup_redirect_client(redirect_client);
13030
13031 // Execute the redirect
13032 return detail::redirect(redirect_client, req, res, path, location, error);
13033 }
13034}
13035
13036// New method for robust client setup (based on basic_manual_redirect.cpp
13037// logic)
13038template <typename ClientType>
13039inline void ClientImpl::setup_redirect_client(ClientType &client) {
13040 // Copy basic settings first
13041 client.set_connection_timeout(connection_timeout_sec_);
13042 client.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
13043 client.set_write_timeout(write_timeout_sec_, write_timeout_usec_);
13044 client.set_keep_alive(keep_alive_);
13045 client.set_follow_location(
13046 true); // Enable redirects to handle multi-step redirects
13047 client.set_path_encode(path_encode_);
13048 client.set_compress(compress_);
13049 client.set_decompress(decompress_);
13050
13051 // NOTE: Authentication credentials (basic auth, bearer token, digest auth)
13052 // are intentionally NOT copied to the redirect client. Per RFC 9110 Section
13053 // 15.4, credentials must not be forwarded when redirecting to a different
13054 // host. This function is only called for cross-host redirects; same-host
13055 // redirects are handled directly in ClientImpl::redirect().
13056
13057 // Setup proxy configuration (CRITICAL ORDER - proxy must be set
13058 // before proxy auth)
13059 if (!proxy_host_.empty() && proxy_port_ != -1) {
13060 // First set proxy host and port
13061 client.set_proxy(proxy_host_, proxy_port_);
13062
13063 // Then set proxy authentication (order matters!)
13064 if (!proxy_basic_auth_username_.empty()) {
13065 client.set_proxy_basic_auth(proxy_basic_auth_username_,
13067 }
13068 if (!proxy_bearer_token_auth_token_.empty()) {
13069 client.set_proxy_bearer_token_auth(proxy_bearer_token_auth_token_);
13070 }
13071#ifdef CPPHTTPLIB_SSL_ENABLED
13072 if (!proxy_digest_auth_username_.empty()) {
13073 client.set_proxy_digest_auth(proxy_digest_auth_username_,
13074 proxy_digest_auth_password_);
13075 }
13076#endif
13077 }
13078
13079 // Copy network and socket settings
13080 client.set_address_family(address_family_);
13081 client.set_tcp_nodelay(tcp_nodelay_);
13082 client.set_ipv6_v6only(ipv6_v6only_);
13083 if (socket_options_) { client.set_socket_options(socket_options_); }
13084 if (!interface_.empty()) { client.set_interface(interface_); }
13085
13086 // Copy logging and headers
13087 if (logger_) { client.set_logger(logger_); }
13088 if (error_logger_) { client.set_error_logger(error_logger_); }
13089
13090 // NOTE: DO NOT copy default_headers_ as they may contain stale Host headers
13091 // Each new client should generate its own headers based on its target host
13092}
13093
13095 const Request &req,
13096 Error &error) const {
13097 auto is_shutting_down = []() { return false; };
13098
13100 auto compressor = compress_ ? detail::create_compressor().first
13101 : std::unique_ptr<detail::compressor>();
13102 if (!compressor) {
13104 }
13105
13107 is_shutting_down, *compressor, error);
13108 } else {
13110 strm, req.content_provider_, 0, req.content_length_, is_shutting_down,
13111 req.upload_progress, error);
13112 }
13113}
13114
13115inline bool ClientImpl::write_request(Stream &strm, Request &req,
13116 bool close_connection, Error &error,
13117 bool skip_body) {
13118 // Prepare additional headers
13119 if (close_connection) {
13120 if (!req.has_header("Connection")) {
13121 req.set_header("Connection", "close");
13122 }
13123 }
13124
13125 std::string ct_for_defaults;
13126 if (!req.has_header("Content-Type") && !req.body.empty()) {
13127 ct_for_defaults = "text/plain";
13128 }
13129 prepare_default_headers(req, false, ct_for_defaults);
13130
13131 if (req.body.empty()) {
13132 if (req.content_provider_) {
13134 if (!req.has_header("Content-Length")) {
13135 auto length = std::to_string(req.content_length_);
13136 req.set_header("Content-Length", length);
13137 }
13138 }
13139 } else {
13140 if (req.method == "POST" || req.method == "PUT" ||
13141 req.method == "PATCH") {
13142 req.set_header("Content-Length", "0");
13143 }
13144 }
13145 }
13146
13147 if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) {
13148 if (!req.has_header("Authorization")) {
13151 }
13152 }
13153
13154 if (!proxy_basic_auth_username_.empty() &&
13155 !proxy_basic_auth_password_.empty()) {
13156 if (!req.has_header("Proxy-Authorization")) {
13159 }
13160 }
13161
13162 if (!bearer_token_auth_token_.empty()) {
13163 if (!req.has_header("Authorization")) {
13165 bearer_token_auth_token_, false));
13166 }
13167 }
13168
13169 if (!proxy_bearer_token_auth_token_.empty()) {
13170 if (!req.has_header("Proxy-Authorization")) {
13173 }
13174 }
13175
13176 // Request line and headers
13177 {
13178 detail::BufferStream bstrm;
13179
13180 // Extract path and query from req.path
13181 std::string path_part, query_part;
13182 auto query_pos = req.path.find('?');
13183 if (query_pos != std::string::npos) {
13184 path_part = req.path.substr(0, query_pos);
13185 query_part = req.path.substr(query_pos + 1);
13186 } else {
13187 path_part = req.path;
13188 query_part = "";
13189 }
13190
13191 // Encode path part. If the original `req.path` already contained a
13192 // query component, preserve its raw query string (including parameter
13193 // order) instead of reparsing and reassembling it which may reorder
13194 // parameters due to container ordering (e.g. `Params` uses
13195 // `std::multimap`). When there is no query in `req.path`, fall back to
13196 // building a query from `req.params` so existing callers that pass
13197 // `Params` continue to work.
13198 auto path_with_query =
13199 path_encode_ ? detail::encode_path(path_part) : path_part;
13200
13201 if (!query_part.empty()) {
13202 // Normalize the query string (decode then re-encode) while preserving
13203 // the original parameter order.
13204 auto normalized = detail::normalize_query_string(query_part);
13205 if (!normalized.empty()) { path_with_query += '?' + normalized; }
13206
13207 // Still populate req.params for handlers/users who read them.
13208 detail::parse_query_text(query_part, req.params);
13209 } else {
13210 // No query in path; parse any query_part (empty) and append params
13211 // from `req.params` when present (preserves prior behavior for
13212 // callers who provide Params separately).
13213 detail::parse_query_text(query_part, req.params);
13214 if (!req.params.empty()) {
13215 path_with_query = append_query_params(path_with_query, req.params);
13216 }
13217 }
13218
13219 // Write request line and headers
13220 detail::write_request_line(bstrm, req.method, path_with_query);
13222 error)) {
13223 output_error_log(error, &req);
13224 return false;
13225 }
13226
13227 // Flush buffer
13228 auto &data = bstrm.get_buffer();
13229 if (!detail::write_data(strm, data.data(), data.size())) {
13230 error = Error::Write;
13231 output_error_log(error, &req);
13232 return false;
13233 }
13234 }
13235
13236 // After sending request line and headers, wait briefly for an early server
13237 // response (e.g. 4xx) and avoid sending a potentially large request body
13238 // unnecessarily. This workaround is only enabled on Windows because Unix
13239 // platforms surface write errors (EPIPE) earlier; on Windows kernel send
13240 // buffering can accept large writes even when the peer already responded.
13241 // Check the stream first (which covers SSL via `is_readable()`), then
13242 // fall back to select on the socket. Only perform the wait for very large
13243 // request bodies to avoid interfering with normal small requests and
13244 // reduce side-effects. Poll briefly (up to 50ms as default) for an early
13245 // response. Skip this check when using Expect: 100-continue, as the protocol
13246 // handles early responses properly.
13247#if defined(_WIN32)
13248 if (!skip_body &&
13251 auto start = std::chrono::high_resolution_clock::now();
13252
13253 for (;;) {
13254 // Prefer socket-level readiness to avoid SSL_pending() false-positives
13255 // from SSL internals. If the underlying socket is readable, assume an
13256 // early response may be present.
13257 auto sock = strm.socket();
13258 if (sock != INVALID_SOCKET && detail::select_read(sock, 0, 0) > 0) {
13259 return false;
13260 }
13261
13262 // Fallback to stream-level check for non-socket streams or when the
13263 // socket isn't reporting readable. Avoid using `is_readable()` for
13264 // SSL, since `SSL_pending()` may report buffered records that do not
13265 // indicate a complete application-level response yet.
13266 if (!is_ssl() && strm.is_readable()) { return false; }
13267
13268 auto now = std::chrono::high_resolution_clock::now();
13269 auto elapsed =
13270 std::chrono::duration_cast<std::chrono::milliseconds>(now - start)
13271 .count();
13273 break;
13274 }
13275
13276 std::this_thread::sleep_for(std::chrono::milliseconds(1));
13277 }
13278 }
13279#endif
13280
13281 // Body
13282 if (skip_body) { return true; }
13283
13284 return write_request_body(strm, req, error);
13285}
13286
13287inline bool ClientImpl::write_request_body(Stream &strm, Request &req,
13288 Error &error) {
13289 if (req.body.empty()) {
13290 return write_content_with_provider(strm, req, error);
13291 }
13292
13293 if (req.upload_progress) {
13294 auto body_size = req.body.size();
13295 size_t written = 0;
13296 auto data = req.body.data();
13297
13298 while (written < body_size) {
13299 size_t to_write = (std::min)(CPPHTTPLIB_SEND_BUFSIZ, body_size - written);
13300 if (!detail::write_data(strm, data + written, to_write)) {
13301 error = Error::Write;
13302 output_error_log(error, &req);
13303 return false;
13304 }
13305 written += to_write;
13306
13307 if (!req.upload_progress(written, body_size)) {
13308 error = Error::Canceled;
13309 output_error_log(error, &req);
13310 return false;
13311 }
13312 }
13313 } else {
13314 if (!detail::write_data(strm, req.body.data(), req.body.size())) {
13315 error = Error::Write;
13316 output_error_log(error, &req);
13317 return false;
13318 }
13319 }
13320
13321 return true;
13322}
13323
13324inline std::unique_ptr<Response>
13325ClientImpl::send_with_content_provider_and_receiver(
13326 Request &req, const char *body, size_t content_length,
13327 ContentProvider content_provider,
13328 ContentProviderWithoutLength content_provider_without_length,
13329 const std::string &content_type, ContentReceiver content_receiver,
13330 Error &error) {
13331 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
13332
13333 auto enc = compress_
13335 : std::pair<std::unique_ptr<detail::compressor>, const char *>(
13336 nullptr, nullptr);
13337
13338 if (enc.second) { req.set_header("Content-Encoding", enc.second); }
13339
13340 if (enc.first && !content_provider_without_length) {
13341 auto &compressor = enc.first;
13342
13343 if (content_provider) {
13344 auto ok = true;
13345 size_t offset = 0;
13346 DataSink data_sink;
13347
13348 data_sink.write = [&](const char *data, size_t data_len) -> bool {
13349 if (ok) {
13350 auto last = offset + data_len == content_length;
13351
13352 auto ret = compressor->compress(
13353 data, data_len, last,
13354 [&](const char *compressed_data, size_t compressed_data_len) {
13355 req.body.append(compressed_data, compressed_data_len);
13356 return true;
13357 });
13358
13359 if (ret) {
13360 offset += data_len;
13361 } else {
13362 ok = false;
13363 }
13364 }
13365 return ok;
13366 };
13367
13368 while (ok && offset < content_length) {
13369 if (!content_provider(offset, content_length - offset, data_sink)) {
13370 error = Error::Canceled;
13371 output_error_log(error, &req);
13372 return nullptr;
13373 }
13374 }
13375 } else {
13376 if (!compressor->compress(body, content_length, true,
13377 [&](const char *data, size_t data_len) {
13378 req.body.append(data, data_len);
13379 return true;
13380 })) {
13381 error = Error::Compression;
13382 output_error_log(error, &req);
13383 return nullptr;
13384 }
13385 }
13386 } else {
13387 if (content_provider) {
13388 req.content_length_ = content_length;
13389 req.content_provider_ = std::move(content_provider);
13390 req.is_chunked_content_provider_ = false;
13391 } else if (content_provider_without_length) {
13392 req.content_length_ = 0;
13393 req.content_provider_ = detail::ContentProviderAdapter(
13394 std::move(content_provider_without_length));
13395 req.is_chunked_content_provider_ = true;
13396 req.set_header("Transfer-Encoding", "chunked");
13397 } else {
13398 req.body.assign(body, content_length);
13399 }
13400 }
13401
13402 if (content_receiver) {
13403 req.content_receiver =
13404 [content_receiver](const char *data, size_t data_length,
13405 size_t /*offset*/, size_t /*total_length*/) {
13406 return content_receiver(data, data_length);
13407 };
13408 }
13409
13410 auto res = detail::make_unique<Response>();
13411 return send(req, *res, error) ? std::move(res) : nullptr;
13412}
13413
13414inline Result ClientImpl::send_with_content_provider_and_receiver(
13415 const std::string &method, const std::string &path, const Headers &headers,
13416 const char *body, size_t content_length, ContentProvider content_provider,
13417 ContentProviderWithoutLength content_provider_without_length,
13418 const std::string &content_type, ContentReceiver content_receiver,
13419 UploadProgress progress) {
13420 Request req;
13421 req.method = method;
13422 req.headers = headers;
13423 req.path = path;
13424 req.upload_progress = std::move(progress);
13425 if (max_timeout_msec_ > 0) {
13426 req.start_time_ = std::chrono::steady_clock::now();
13427 }
13428
13429 auto error = Error::Success;
13430
13431 auto res = send_with_content_provider_and_receiver(
13432 req, body, content_length, std::move(content_provider),
13433 std::move(content_provider_without_length), content_type,
13434 std::move(content_receiver), error);
13435
13436#ifdef CPPHTTPLIB_SSL_ENABLED
13437 return Result{std::move(res), error, std::move(req.headers), last_ssl_error_,
13438 last_backend_error_};
13439#else
13440 return Result{std::move(res), error, std::move(req.headers)};
13441#endif
13442}
13443
13444inline void ClientImpl::output_log(const Request &req,
13445 const Response &res) const {
13446 if (logger_) {
13447 std::lock_guard<std::mutex> guard(logger_mutex_);
13448 logger_(req, res);
13449 }
13450}
13451
13453 const Request *req) const {
13454 if (error_logger_) {
13455 std::lock_guard<std::mutex> guard(logger_mutex_);
13456 error_logger_(err, req);
13457 }
13458}
13459
13461 Response &res, bool close_connection,
13462 Error &error) {
13463 // Auto-add Expect: 100-continue for large bodies
13464 if (CPPHTTPLIB_EXPECT_100_THRESHOLD > 0 && !req.has_header("Expect")) {
13465 auto body_size = req.body.empty() ? req.content_length_ : req.body.size();
13466 if (body_size >= CPPHTTPLIB_EXPECT_100_THRESHOLD) {
13467 req.set_header("Expect", "100-continue");
13468 }
13469 }
13470
13471 // Check for Expect: 100-continue
13472 auto expect_100_continue = req.get_header_value("Expect") == "100-continue";
13473
13474 // Send request (skip body if using Expect: 100-continue)
13475 auto write_request_success =
13476 write_request(strm, req, close_connection, error, expect_100_continue);
13477
13478#ifdef CPPHTTPLIB_SSL_ENABLED
13479 if (is_ssl() && !expect_100_continue) {
13480 auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1;
13481 if (!is_proxy_enabled) {
13482 if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
13484 output_error_log(error, &req);
13485 return false;
13486 }
13487 }
13488 }
13489#endif
13490
13491 // Handle Expect: 100-continue with timeout
13492 if (expect_100_continue && CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND > 0) {
13493 time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000;
13494 time_t usec = (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000) * 1000;
13495 auto ret = detail::select_read(strm.socket(), sec, usec);
13496 if (ret <= 0) {
13497 // Timeout or error: send body anyway (server didn't respond in time)
13498 if (!write_request_body(strm, req, error)) { return false; }
13499 expect_100_continue = false; // Switch to normal response handling
13500 }
13501 }
13502
13503 // Receive response and headers
13504 // When using Expect: 100-continue, don't auto-skip `100 Continue` response
13505 if (!read_response_line(strm, req, res, !expect_100_continue) ||
13506 !detail::read_headers(strm, res.headers)) {
13507 if (write_request_success) { error = Error::Read; }
13508 output_error_log(error, &req);
13509 return false;
13510 }
13511
13512 if (!write_request_success) { return false; }
13513
13514 // Handle Expect: 100-continue response
13515 if (expect_100_continue) {
13516 if (res.status == StatusCode::Continue_100) {
13517 // Server accepted, send the body
13518 if (!write_request_body(strm, req, error)) { return false; }
13519
13520 // Read the actual response
13521 res.headers.clear();
13522 res.body.clear();
13523 if (!read_response_line(strm, req, res) ||
13524 !detail::read_headers(strm, res.headers)) {
13525 error = Error::Read;
13526 output_error_log(error, &req);
13527 return false;
13528 }
13529 }
13530 // If not 100 Continue, server returned an error; proceed with that response
13531 }
13532
13533 // Body
13534 if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
13535 req.method != "CONNECT") {
13536 auto redirect = 300 < res.status && res.status < 400 &&
13539
13540 if (req.response_handler && !redirect) {
13541 if (!req.response_handler(res)) {
13542 error = Error::Canceled;
13543 output_error_log(error, &req);
13544 return false;
13545 }
13546 }
13547
13548 auto out =
13550 ? static_cast<ContentReceiverWithProgress>(
13551 [&](const char *buf, size_t n, size_t off, size_t len) {
13552 if (redirect) { return true; }
13553 auto ret = req.content_receiver(buf, n, off, len);
13554 if (!ret) {
13555 error = Error::Canceled;
13556 output_error_log(error, &req);
13557 }
13558 return ret;
13559 })
13560 : static_cast<ContentReceiverWithProgress>(
13561 [&](const char *buf, size_t n, size_t /*off*/,
13562 size_t /*len*/) {
13563 assert(res.body.size() + n <= res.body.max_size());
13564 if (payload_max_length_ > 0 &&
13565 (res.body.size() >= payload_max_length_ ||
13566 n > payload_max_length_ - res.body.size())) {
13567 return false;
13568 }
13569 res.body.append(buf, n);
13570 return true;
13571 });
13572
13573 auto progress = [&](size_t current, size_t total) {
13574 if (!req.download_progress || redirect) { return true; }
13575 auto ret = req.download_progress(current, total);
13576 if (!ret) {
13577 error = Error::Canceled;
13578 output_error_log(error, &req);
13579 }
13580 return ret;
13581 };
13582
13583 if (res.has_header("Content-Length")) {
13584 if (!req.content_receiver) {
13585 auto len = res.get_header_value_u64("Content-Length");
13586 if (len > res.body.max_size()) {
13587 error = Error::Read;
13588 output_error_log(error, &req);
13589 return false;
13590 }
13591 res.body.reserve(static_cast<size_t>(len));
13592 }
13593 }
13594
13596 int dummy_status;
13597 auto max_length = (!has_payload_max_length_ && req.content_receiver)
13598 ? (std::numeric_limits<size_t>::max)()
13600 if (!detail::read_content(strm, res, max_length, dummy_status,
13601 std::move(progress), std::move(out),
13602 decompress_)) {
13603 if (error != Error::Canceled) { error = Error::Read; }
13604 output_error_log(error, &req);
13605 return false;
13606 }
13607 }
13608 }
13609
13610 // Log
13611 output_log(req, res);
13612
13613 return true;
13614}
13615
13616inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
13617 const std::string &boundary, const UploadFormDataItems &items,
13618 const FormDataProviderItems &provider_items) const {
13619 size_t cur_item = 0;
13620 size_t cur_start = 0;
13621 // cur_item and cur_start are copied to within the std::function and
13622 // maintain state between successive calls
13623 return [&, cur_item, cur_start](size_t offset,
13624 DataSink &sink) mutable -> bool {
13625 if (!offset && !items.empty()) {
13626 sink.os << detail::serialize_multipart_formdata(items, boundary, false);
13627 return true;
13628 } else if (cur_item < provider_items.size()) {
13629 if (!cur_start) {
13630 const auto &begin = detail::serialize_multipart_formdata_item_begin(
13631 provider_items[cur_item], boundary);
13632 offset += begin.size();
13633 cur_start = offset;
13634 sink.os << begin;
13635 }
13636
13637 DataSink cur_sink;
13638 auto has_data = true;
13639 cur_sink.write = sink.write;
13640 cur_sink.done = [&]() { has_data = false; };
13641
13642 if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) {
13643 return false;
13644 }
13645
13646 if (!has_data) {
13647 sink.os << detail::serialize_multipart_formdata_item_end();
13648 cur_item++;
13649 cur_start = 0;
13650 }
13651 return true;
13652 } else {
13653 sink.os << detail::serialize_multipart_formdata_finish(boundary);
13654 sink.done();
13655 return true;
13656 }
13657 };
13658}
13659
13660inline bool ClientImpl::process_socket(
13661 const Socket &socket,
13662 std::chrono::time_point<std::chrono::steady_clock> start_time,
13663 std::function<bool(Stream &strm)> callback) {
13664 return detail::process_client_socket(
13665 socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
13666 write_timeout_usec_, max_timeout_msec_, start_time, std::move(callback));
13667}
13668
13669inline bool ClientImpl::is_ssl() const { return false; }
13670
13671inline Result ClientImpl::Get(const std::string &path,
13672 DownloadProgress progress) {
13673 return Get(path, Headers(), std::move(progress));
13674}
13675
13676inline Result ClientImpl::Get(const std::string &path, const Params &params,
13677 const Headers &headers,
13678 DownloadProgress progress) {
13679 if (params.empty()) { return Get(path, headers); }
13680
13681 std::string path_with_query = append_query_params(path, params);
13682 return Get(path_with_query, headers, std::move(progress));
13683}
13684
13685inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
13686 DownloadProgress progress) {
13687 Request req;
13688 req.method = "GET";
13689 req.path = path;
13690 req.headers = headers;
13691 req.download_progress = std::move(progress);
13692 if (max_timeout_msec_ > 0) {
13693 req.start_time_ = std::chrono::steady_clock::now();
13694 }
13695
13696 return send_(std::move(req));
13697}
13698
13699inline Result ClientImpl::Get(const std::string &path,
13700 ContentReceiver content_receiver,
13701 DownloadProgress progress) {
13702 return Get(path, Headers(), nullptr, std::move(content_receiver),
13703 std::move(progress));
13704}
13705
13706inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
13707 ContentReceiver content_receiver,
13708 DownloadProgress progress) {
13709 return Get(path, headers, nullptr, std::move(content_receiver),
13710 std::move(progress));
13711}
13712
13713inline Result ClientImpl::Get(const std::string &path,
13714 ResponseHandler response_handler,
13715 ContentReceiver content_receiver,
13716 DownloadProgress progress) {
13717 return Get(path, Headers(), std::move(response_handler),
13718 std::move(content_receiver), std::move(progress));
13719}
13720
13721inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
13722 ResponseHandler response_handler,
13723 ContentReceiver content_receiver,
13724 DownloadProgress progress) {
13725 Request req;
13726 req.method = "GET";
13727 req.path = path;
13728 req.headers = headers;
13729 req.response_handler = std::move(response_handler);
13730 req.content_receiver =
13731 [content_receiver](const char *data, size_t data_length,
13732 size_t /*offset*/, size_t /*total_length*/) {
13733 return content_receiver(data, data_length);
13734 };
13735 req.download_progress = std::move(progress);
13736 if (max_timeout_msec_ > 0) {
13737 req.start_time_ = std::chrono::steady_clock::now();
13738 }
13739
13740 return send_(std::move(req));
13741}
13742
13743inline Result ClientImpl::Get(const std::string &path, const Params &params,
13744 const Headers &headers,
13745 ContentReceiver content_receiver,
13746 DownloadProgress progress) {
13747 return Get(path, params, headers, nullptr, std::move(content_receiver),
13748 std::move(progress));
13749}
13750
13751inline Result ClientImpl::Get(const std::string &path, const Params &params,
13752 const Headers &headers,
13753 ResponseHandler response_handler,
13754 ContentReceiver content_receiver,
13755 DownloadProgress progress) {
13756 if (params.empty()) {
13757 return Get(path, headers, std::move(response_handler),
13758 std::move(content_receiver), std::move(progress));
13759 }
13760
13761 std::string path_with_query = append_query_params(path, params);
13762 return Get(path_with_query, headers, std::move(response_handler),
13763 std::move(content_receiver), std::move(progress));
13764}
13765
13766inline Result ClientImpl::Head(const std::string &path) {
13767 return Head(path, Headers());
13768}
13769
13770inline Result ClientImpl::Head(const std::string &path,
13771 const Headers &headers) {
13772 Request req;
13773 req.method = "HEAD";
13774 req.headers = headers;
13775 req.path = path;
13776 if (max_timeout_msec_ > 0) {
13777 req.start_time_ = std::chrono::steady_clock::now();
13778 }
13779
13780 return send_(std::move(req));
13781}
13782
13783inline Result ClientImpl::Post(const std::string &path) {
13784 return Post(path, std::string(), std::string());
13785}
13786
13787inline Result ClientImpl::Post(const std::string &path,
13788 const Headers &headers) {
13789 return Post(path, headers, nullptr, 0, std::string());
13790}
13791
13792inline Result ClientImpl::Post(const std::string &path, const char *body,
13793 size_t content_length,
13794 const std::string &content_type,
13795 UploadProgress progress) {
13796 return Post(path, Headers(), body, content_length, content_type, progress);
13797}
13798
13799inline Result ClientImpl::Post(const std::string &path, const std::string &body,
13800 const std::string &content_type,
13801 UploadProgress progress) {
13802 return Post(path, Headers(), body, content_type, progress);
13803}
13804
13805inline Result ClientImpl::Post(const std::string &path, const Params &params) {
13806 return Post(path, Headers(), params);
13807}
13808
13809inline Result ClientImpl::Post(const std::string &path, size_t content_length,
13810 ContentProvider content_provider,
13811 const std::string &content_type,
13812 UploadProgress progress) {
13813 return Post(path, Headers(), content_length, std::move(content_provider),
13814 content_type, progress);
13815}
13816
13817inline Result ClientImpl::Post(const std::string &path, size_t content_length,
13818 ContentProvider content_provider,
13819 const std::string &content_type,
13820 ContentReceiver content_receiver,
13821 UploadProgress progress) {
13822 return Post(path, Headers(), content_length, std::move(content_provider),
13823 content_type, std::move(content_receiver), progress);
13824}
13825
13826inline Result ClientImpl::Post(const std::string &path,
13827 ContentProviderWithoutLength content_provider,
13828 const std::string &content_type,
13829 UploadProgress progress) {
13830 return Post(path, Headers(), std::move(content_provider), content_type,
13831 progress);
13832}
13833
13834inline Result ClientImpl::Post(const std::string &path,
13835 ContentProviderWithoutLength content_provider,
13836 const std::string &content_type,
13837 ContentReceiver content_receiver,
13838 UploadProgress progress) {
13839 return Post(path, Headers(), std::move(content_provider), content_type,
13840 std::move(content_receiver), progress);
13841}
13842
13843inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
13844 const Params &params) {
13845 auto query = detail::params_to_query_str(params);
13846 return Post(path, headers, query, "application/x-www-form-urlencoded");
13847}
13848
13849inline Result ClientImpl::Post(const std::string &path,
13850 const UploadFormDataItems &items,
13851 UploadProgress progress) {
13852 return Post(path, Headers(), items, progress);
13853}
13854
13855inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
13856 const UploadFormDataItems &items,
13857 UploadProgress progress) {
13858 const auto &boundary = detail::make_multipart_data_boundary();
13859 const auto &content_type =
13861 auto content_length = detail::get_multipart_content_length(items, boundary);
13862 return Post(path, headers, content_length,
13864 content_type, progress);
13865}
13866
13867inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
13868 const UploadFormDataItems &items,
13869 const std::string &boundary,
13870 UploadProgress progress) {
13873 }
13874
13875 const auto &content_type =
13877 auto content_length = detail::get_multipart_content_length(items, boundary);
13878 return Post(path, headers, content_length,
13880 content_type, progress);
13881}
13882
13883inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
13884 const char *body, size_t content_length,
13885 const std::string &content_type,
13886 UploadProgress progress) {
13887 return send_with_content_provider_and_receiver(
13888 "POST", path, headers, body, content_length, nullptr, nullptr,
13889 content_type, nullptr, progress);
13890}
13891
13892inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
13893 const std::string &body,
13894 const std::string &content_type,
13895 UploadProgress progress) {
13896 return send_with_content_provider_and_receiver(
13897 "POST", path, headers, body.data(), body.size(), nullptr, nullptr,
13898 content_type, nullptr, progress);
13899}
13900
13901inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
13902 size_t content_length,
13903 ContentProvider content_provider,
13904 const std::string &content_type,
13905 UploadProgress progress) {
13906 return send_with_content_provider_and_receiver(
13907 "POST", path, headers, nullptr, content_length,
13908 std::move(content_provider), nullptr, content_type, nullptr, progress);
13909}
13910
13911inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
13912 size_t content_length,
13913 ContentProvider content_provider,
13914 const std::string &content_type,
13915 ContentReceiver content_receiver,
13916 DownloadProgress progress) {
13917 return send_with_content_provider_and_receiver(
13918 "POST", path, headers, nullptr, content_length,
13919 std::move(content_provider), nullptr, content_type,
13920 std::move(content_receiver), std::move(progress));
13921}
13922
13923inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
13924 ContentProviderWithoutLength content_provider,
13925 const std::string &content_type,
13926 UploadProgress progress) {
13927 return send_with_content_provider_and_receiver(
13928 "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
13929 content_type, nullptr, progress);
13930}
13931
13932inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
13933 ContentProviderWithoutLength content_provider,
13934 const std::string &content_type,
13935 ContentReceiver content_receiver,
13936 DownloadProgress progress) {
13937 return send_with_content_provider_and_receiver(
13938 "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
13939 content_type, std::move(content_receiver), std::move(progress));
13940}
13941
13942inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
13943 const UploadFormDataItems &items,
13944 const FormDataProviderItems &provider_items,
13945 UploadProgress progress) {
13946 const auto &boundary = detail::make_multipart_data_boundary();
13947 const auto &content_type =
13949 return send_with_content_provider_and_receiver(
13950 "POST", path, headers, nullptr, 0, nullptr,
13951 get_multipart_content_provider(boundary, items, provider_items),
13952 content_type, nullptr, progress);
13953}
13954
13955inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
13956 const std::string &body,
13957 const std::string &content_type,
13958 ContentReceiver content_receiver,
13959 DownloadProgress progress) {
13960 Request req;
13961 req.method = "POST";
13962 req.path = path;
13963 req.headers = headers;
13964 req.body = body;
13965 req.content_receiver =
13966 [content_receiver](const char *data, size_t data_length,
13967 size_t /*offset*/, size_t /*total_length*/) {
13968 return content_receiver(data, data_length);
13969 };
13970 req.download_progress = std::move(progress);
13971
13972 if (max_timeout_msec_ > 0) {
13973 req.start_time_ = std::chrono::steady_clock::now();
13974 }
13975
13976 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
13977
13978 return send_(std::move(req));
13979}
13980
13981inline Result ClientImpl::Put(const std::string &path) {
13982 return Put(path, std::string(), std::string());
13983}
13984
13985inline Result ClientImpl::Put(const std::string &path, const Headers &headers) {
13986 return Put(path, headers, nullptr, 0, std::string());
13987}
13988
13989inline Result ClientImpl::Put(const std::string &path, const char *body,
13990 size_t content_length,
13991 const std::string &content_type,
13992 UploadProgress progress) {
13993 return Put(path, Headers(), body, content_length, content_type, progress);
13994}
13995
13996inline Result ClientImpl::Put(const std::string &path, const std::string &body,
13997 const std::string &content_type,
13998 UploadProgress progress) {
13999 return Put(path, Headers(), body, content_type, progress);
14000}
14001
14002inline Result ClientImpl::Put(const std::string &path, const Params &params) {
14003 return Put(path, Headers(), params);
14004}
14005
14006inline Result ClientImpl::Put(const std::string &path, size_t content_length,
14007 ContentProvider content_provider,
14008 const std::string &content_type,
14009 UploadProgress progress) {
14010 return Put(path, Headers(), content_length, std::move(content_provider),
14011 content_type, progress);
14012}
14013
14014inline Result ClientImpl::Put(const std::string &path, size_t content_length,
14015 ContentProvider content_provider,
14016 const std::string &content_type,
14017 ContentReceiver content_receiver,
14018 UploadProgress progress) {
14019 return Put(path, Headers(), content_length, std::move(content_provider),
14020 content_type, std::move(content_receiver), progress);
14021}
14022
14023inline Result ClientImpl::Put(const std::string &path,
14024 ContentProviderWithoutLength content_provider,
14025 const std::string &content_type,
14026 UploadProgress progress) {
14027 return Put(path, Headers(), std::move(content_provider), content_type,
14028 progress);
14029}
14030
14031inline Result ClientImpl::Put(const std::string &path,
14032 ContentProviderWithoutLength content_provider,
14033 const std::string &content_type,
14034 ContentReceiver content_receiver,
14035 UploadProgress progress) {
14036 return Put(path, Headers(), std::move(content_provider), content_type,
14037 std::move(content_receiver), progress);
14038}
14039
14040inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14041 const Params &params) {
14042 auto query = detail::params_to_query_str(params);
14043 return Put(path, headers, query, "application/x-www-form-urlencoded");
14044}
14045
14046inline Result ClientImpl::Put(const std::string &path,
14047 const UploadFormDataItems &items,
14048 UploadProgress progress) {
14049 return Put(path, Headers(), items, progress);
14050}
14051
14052inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14053 const UploadFormDataItems &items,
14054 UploadProgress progress) {
14055 const auto &boundary = detail::make_multipart_data_boundary();
14056 const auto &content_type =
14058 auto content_length = detail::get_multipart_content_length(items, boundary);
14059 return Put(path, headers, content_length,
14061 content_type, progress);
14062}
14063
14064inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14065 const UploadFormDataItems &items,
14066 const std::string &boundary,
14067 UploadProgress progress) {
14070 }
14071
14072 const auto &content_type =
14074 auto content_length = detail::get_multipart_content_length(items, boundary);
14075 return Put(path, headers, content_length,
14077 content_type, progress);
14078}
14079
14080inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14081 const char *body, size_t content_length,
14082 const std::string &content_type,
14083 UploadProgress progress) {
14084 return send_with_content_provider_and_receiver(
14085 "PUT", path, headers, body, content_length, nullptr, nullptr,
14086 content_type, nullptr, progress);
14087}
14088
14089inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14090 const std::string &body,
14091 const std::string &content_type,
14092 UploadProgress progress) {
14093 return send_with_content_provider_and_receiver(
14094 "PUT", path, headers, body.data(), body.size(), nullptr, nullptr,
14095 content_type, nullptr, progress);
14096}
14097
14098inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14099 size_t content_length,
14100 ContentProvider content_provider,
14101 const std::string &content_type,
14102 UploadProgress progress) {
14103 return send_with_content_provider_and_receiver(
14104 "PUT", path, headers, nullptr, content_length,
14105 std::move(content_provider), nullptr, content_type, nullptr, progress);
14106}
14107
14108inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14109 size_t content_length,
14110 ContentProvider content_provider,
14111 const std::string &content_type,
14112 ContentReceiver content_receiver,
14113 UploadProgress progress) {
14114 return send_with_content_provider_and_receiver(
14115 "PUT", path, headers, nullptr, content_length,
14116 std::move(content_provider), nullptr, content_type,
14117 std::move(content_receiver), progress);
14118}
14119
14120inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14121 ContentProviderWithoutLength content_provider,
14122 const std::string &content_type,
14123 UploadProgress progress) {
14124 return send_with_content_provider_and_receiver(
14125 "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14126 content_type, nullptr, progress);
14127}
14128
14129inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14130 ContentProviderWithoutLength content_provider,
14131 const std::string &content_type,
14132 ContentReceiver content_receiver,
14133 UploadProgress progress) {
14134 return send_with_content_provider_and_receiver(
14135 "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14136 content_type, std::move(content_receiver), progress);
14137}
14138
14139inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14140 const UploadFormDataItems &items,
14141 const FormDataProviderItems &provider_items,
14142 UploadProgress progress) {
14143 const auto &boundary = detail::make_multipart_data_boundary();
14144 const auto &content_type =
14146 return send_with_content_provider_and_receiver(
14147 "PUT", path, headers, nullptr, 0, nullptr,
14148 get_multipart_content_provider(boundary, items, provider_items),
14149 content_type, nullptr, progress);
14150}
14151
14152inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14153 const std::string &body,
14154 const std::string &content_type,
14155 ContentReceiver content_receiver,
14156 DownloadProgress progress) {
14157 Request req;
14158 req.method = "PUT";
14159 req.path = path;
14160 req.headers = headers;
14161 req.body = body;
14162 req.content_receiver =
14163 [content_receiver](const char *data, size_t data_length,
14164 size_t /*offset*/, size_t /*total_length*/) {
14165 return content_receiver(data, data_length);
14166 };
14167 req.download_progress = std::move(progress);
14168
14169 if (max_timeout_msec_ > 0) {
14170 req.start_time_ = std::chrono::steady_clock::now();
14171 }
14172
14173 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14174
14175 return send_(std::move(req));
14176}
14177
14178inline Result ClientImpl::Patch(const std::string &path) {
14179 return Patch(path, std::string(), std::string());
14180}
14181
14182inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14183 UploadProgress progress) {
14184 return Patch(path, headers, nullptr, 0, std::string(), progress);
14185}
14186
14187inline Result ClientImpl::Patch(const std::string &path, const char *body,
14188 size_t content_length,
14189 const std::string &content_type,
14190 UploadProgress progress) {
14191 return Patch(path, Headers(), body, content_length, content_type, progress);
14192}
14193
14194inline Result ClientImpl::Patch(const std::string &path,
14195 const std::string &body,
14196 const std::string &content_type,
14197 UploadProgress progress) {
14198 return Patch(path, Headers(), body, content_type, progress);
14199}
14200
14201inline Result ClientImpl::Patch(const std::string &path, const Params &params) {
14202 return Patch(path, Headers(), params);
14203}
14204
14205inline Result ClientImpl::Patch(const std::string &path, size_t content_length,
14206 ContentProvider content_provider,
14207 const std::string &content_type,
14208 UploadProgress progress) {
14209 return Patch(path, Headers(), content_length, std::move(content_provider),
14210 content_type, progress);
14211}
14212
14213inline Result ClientImpl::Patch(const std::string &path, size_t content_length,
14214 ContentProvider content_provider,
14215 const std::string &content_type,
14216 ContentReceiver content_receiver,
14217 UploadProgress progress) {
14218 return Patch(path, Headers(), content_length, std::move(content_provider),
14219 content_type, std::move(content_receiver), progress);
14220}
14221
14222inline Result ClientImpl::Patch(const std::string &path,
14223 ContentProviderWithoutLength content_provider,
14224 const std::string &content_type,
14225 UploadProgress progress) {
14226 return Patch(path, Headers(), std::move(content_provider), content_type,
14227 progress);
14228}
14229
14230inline Result ClientImpl::Patch(const std::string &path,
14231 ContentProviderWithoutLength content_provider,
14232 const std::string &content_type,
14233 ContentReceiver content_receiver,
14234 UploadProgress progress) {
14235 return Patch(path, Headers(), std::move(content_provider), content_type,
14236 std::move(content_receiver), progress);
14237}
14238
14239inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14240 const Params &params) {
14241 auto query = detail::params_to_query_str(params);
14242 return Patch(path, headers, query, "application/x-www-form-urlencoded");
14243}
14244
14245inline Result ClientImpl::Patch(const std::string &path,
14246 const UploadFormDataItems &items,
14247 UploadProgress progress) {
14248 return Patch(path, Headers(), items, progress);
14249}
14250
14251inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14252 const UploadFormDataItems &items,
14253 UploadProgress progress) {
14254 const auto &boundary = detail::make_multipart_data_boundary();
14255 const auto &content_type =
14257 auto content_length = detail::get_multipart_content_length(items, boundary);
14258 return Patch(path, headers, content_length,
14260 content_type, progress);
14261}
14262
14263inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14264 const UploadFormDataItems &items,
14265 const std::string &boundary,
14266 UploadProgress progress) {
14269 }
14270
14271 const auto &content_type =
14273 auto content_length = detail::get_multipart_content_length(items, boundary);
14274 return Patch(path, headers, content_length,
14276 content_type, progress);
14277}
14278
14279inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14280 const char *body, size_t content_length,
14281 const std::string &content_type,
14282 UploadProgress progress) {
14283 return send_with_content_provider_and_receiver(
14284 "PATCH", path, headers, body, content_length, nullptr, nullptr,
14285 content_type, nullptr, progress);
14286}
14287
14288inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14289 const std::string &body,
14290 const std::string &content_type,
14291 UploadProgress progress) {
14292 return send_with_content_provider_and_receiver(
14293 "PATCH", path, headers, body.data(), body.size(), nullptr, nullptr,
14294 content_type, nullptr, progress);
14295}
14296
14297inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14298 size_t content_length,
14299 ContentProvider content_provider,
14300 const std::string &content_type,
14301 UploadProgress progress) {
14302 return send_with_content_provider_and_receiver(
14303 "PATCH", path, headers, nullptr, content_length,
14304 std::move(content_provider), nullptr, content_type, nullptr, progress);
14305}
14306
14307inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14308 size_t content_length,
14309 ContentProvider content_provider,
14310 const std::string &content_type,
14311 ContentReceiver content_receiver,
14312 UploadProgress progress) {
14313 return send_with_content_provider_and_receiver(
14314 "PATCH", path, headers, nullptr, content_length,
14315 std::move(content_provider), nullptr, content_type,
14316 std::move(content_receiver), progress);
14317}
14318
14319inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14320 ContentProviderWithoutLength content_provider,
14321 const std::string &content_type,
14322 UploadProgress progress) {
14323 return send_with_content_provider_and_receiver(
14324 "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14325 content_type, nullptr, progress);
14326}
14327
14328inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14329 ContentProviderWithoutLength content_provider,
14330 const std::string &content_type,
14331 ContentReceiver content_receiver,
14332 UploadProgress progress) {
14333 return send_with_content_provider_and_receiver(
14334 "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14335 content_type, std::move(content_receiver), progress);
14336}
14337
14338inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14339 const UploadFormDataItems &items,
14340 const FormDataProviderItems &provider_items,
14341 UploadProgress progress) {
14342 const auto &boundary = detail::make_multipart_data_boundary();
14343 const auto &content_type =
14345 return send_with_content_provider_and_receiver(
14346 "PATCH", path, headers, nullptr, 0, nullptr,
14347 get_multipart_content_provider(boundary, items, provider_items),
14348 content_type, nullptr, progress);
14349}
14350
14351inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14352 const std::string &body,
14353 const std::string &content_type,
14354 ContentReceiver content_receiver,
14355 DownloadProgress progress) {
14356 Request req;
14357 req.method = "PATCH";
14358 req.path = path;
14359 req.headers = headers;
14360 req.body = body;
14361 req.content_receiver =
14362 [content_receiver](const char *data, size_t data_length,
14363 size_t /*offset*/, size_t /*total_length*/) {
14364 return content_receiver(data, data_length);
14365 };
14366 req.download_progress = std::move(progress);
14367
14368 if (max_timeout_msec_ > 0) {
14369 req.start_time_ = std::chrono::steady_clock::now();
14370 }
14371
14372 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14373
14374 return send_(std::move(req));
14375}
14376
14377inline Result ClientImpl::Delete(const std::string &path,
14378 DownloadProgress progress) {
14379 return Delete(path, Headers(), std::string(), std::string(), progress);
14380}
14381
14382inline Result ClientImpl::Delete(const std::string &path,
14383 const Headers &headers,
14384 DownloadProgress progress) {
14385 return Delete(path, headers, std::string(), std::string(), progress);
14386}
14387
14388inline Result ClientImpl::Delete(const std::string &path, const char *body,
14389 size_t content_length,
14390 const std::string &content_type,
14391 DownloadProgress progress) {
14392 return Delete(path, Headers(), body, content_length, content_type, progress);
14393}
14394
14395inline Result ClientImpl::Delete(const std::string &path,
14396 const std::string &body,
14397 const std::string &content_type,
14398 DownloadProgress progress) {
14399 return Delete(path, Headers(), body.data(), body.size(), content_type,
14400 progress);
14401}
14402
14403inline Result ClientImpl::Delete(const std::string &path,
14404 const Headers &headers,
14405 const std::string &body,
14406 const std::string &content_type,
14407 DownloadProgress progress) {
14408 return Delete(path, headers, body.data(), body.size(), content_type,
14409 progress);
14410}
14411
14412inline Result ClientImpl::Delete(const std::string &path, const Params &params,
14413 DownloadProgress progress) {
14414 return Delete(path, Headers(), params, progress);
14415}
14416
14417inline Result ClientImpl::Delete(const std::string &path,
14418 const Headers &headers, const Params &params,
14419 DownloadProgress progress) {
14420 auto query = detail::params_to_query_str(params);
14421 return Delete(path, headers, query, "application/x-www-form-urlencoded",
14422 progress);
14423}
14424
14425inline Result ClientImpl::Delete(const std::string &path,
14426 const Headers &headers, const char *body,
14427 size_t content_length,
14428 const std::string &content_type,
14429 DownloadProgress progress) {
14430 Request req;
14431 req.method = "DELETE";
14432 req.headers = headers;
14433 req.path = path;
14434 req.download_progress = std::move(progress);
14435 if (max_timeout_msec_ > 0) {
14436 req.start_time_ = std::chrono::steady_clock::now();
14437 }
14438
14439 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14440 req.body.assign(body, content_length);
14441
14442 return send_(std::move(req));
14443}
14444
14445inline Result ClientImpl::Options(const std::string &path) {
14446 return Options(path, Headers());
14447}
14448
14449inline Result ClientImpl::Options(const std::string &path,
14450 const Headers &headers) {
14451 Request req;
14452 req.method = "OPTIONS";
14453 req.headers = headers;
14454 req.path = path;
14455 if (max_timeout_msec_ > 0) {
14456 req.start_time_ = std::chrono::steady_clock::now();
14457 }
14458
14459 return send_(std::move(req));
14460}
14461
14462inline void ClientImpl::stop() {
14463 std::lock_guard<std::mutex> guard(socket_mutex_);
14464
14465 // If there is anything ongoing right now, the ONLY thread-safe thing we can
14466 // do is to shutdown_socket, so that threads using this socket suddenly
14467 // discover they can't read/write any more and error out. Everything else
14468 // (closing the socket, shutting ssl down) is unsafe because these actions
14469 // are not thread-safe.
14472
14473 // Aside from that, we set a flag for the socket to be closed when we're
14474 // done.
14476 return;
14477 }
14478
14479 // Otherwise, still holding the mutex, we can shut everything down ourselves
14480 shutdown_ssl(socket_, true);
14483}
14484
14485inline std::string ClientImpl::host() const { return host_; }
14486
14487inline int ClientImpl::port() const { return port_; }
14488
14489inline size_t ClientImpl::is_socket_open() const {
14490 std::lock_guard<std::mutex> guard(socket_mutex_);
14491 return socket_.is_open();
14492}
14493
14494inline socket_t ClientImpl::socket() const { return socket_.sock; }
14495
14496inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
14499}
14500
14501inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
14502 read_timeout_sec_ = sec;
14503 read_timeout_usec_ = usec;
14504}
14505
14506inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
14507 write_timeout_sec_ = sec;
14508 write_timeout_usec_ = usec;
14509}
14510
14511inline void ClientImpl::set_max_timeout(time_t msec) {
14512 max_timeout_msec_ = msec;
14513}
14514
14515inline void ClientImpl::set_basic_auth(const std::string &username,
14516 const std::string &password) {
14517 basic_auth_username_ = username;
14518 basic_auth_password_ = password;
14519}
14520
14521inline void ClientImpl::set_bearer_token_auth(const std::string &token) {
14523}
14524
14525inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
14526
14528
14529inline void ClientImpl::set_path_encode(bool on) { path_encode_ = on; }
14530
14531inline void
14532ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
14533 addr_map_ = std::move(addr_map);
14534}
14535
14537 default_headers_ = std::move(headers);
14538}
14539
14541 std::function<ssize_t(Stream &, Headers &)> const &writer) {
14542 header_writer_ = writer;
14543}
14544
14545inline void ClientImpl::set_address_family(int family) {
14546 address_family_ = family;
14547}
14548
14549inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
14550
14551inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
14552
14554 socket_options_ = std::move(socket_options);
14555}
14556
14557inline void ClientImpl::set_compress(bool on) { compress_ = on; }
14558
14559inline void ClientImpl::set_decompress(bool on) { decompress_ = on; }
14560
14561inline void ClientImpl::set_payload_max_length(size_t length) {
14562 payload_max_length_ = length;
14564}
14565
14566inline void ClientImpl::set_interface(const std::string &intf) {
14567 interface_ = intf;
14568}
14569
14570inline void ClientImpl::set_proxy(const std::string &host, int port) {
14571 proxy_host_ = host;
14572 proxy_port_ = port;
14573}
14574
14575inline void ClientImpl::set_proxy_basic_auth(const std::string &username,
14576 const std::string &password) {
14577 proxy_basic_auth_username_ = username;
14578 proxy_basic_auth_password_ = password;
14579}
14580
14581inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) {
14583}
14584
14585#ifdef CPPHTTPLIB_SSL_ENABLED
14586inline void ClientImpl::set_digest_auth(const std::string &username,
14587 const std::string &password) {
14588 digest_auth_username_ = username;
14589 digest_auth_password_ = password;
14590}
14591
14592inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path,
14593 const std::string &ca_cert_dir_path) {
14594 ca_cert_file_path_ = ca_cert_file_path;
14595 ca_cert_dir_path_ = ca_cert_dir_path;
14596}
14597
14598inline void ClientImpl::set_proxy_digest_auth(const std::string &username,
14599 const std::string &password) {
14600 proxy_digest_auth_username_ = username;
14601 proxy_digest_auth_password_ = password;
14602}
14603
14604inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
14605 server_certificate_verification_ = enabled;
14606}
14607
14608inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
14609 server_hostname_verification_ = enabled;
14610}
14611#endif
14612
14613// ClientImpl::set_ca_cert_store is defined after TLS namespace (uses helpers)
14614#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
14615inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert,
14616 std::size_t size) const {
14617 auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
14618 auto se = detail::scope_exit([&] { BIO_free_all(mem); });
14619 if (!mem) { return nullptr; }
14620
14621 auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
14622 if (!inf) { return nullptr; }
14623
14624 auto cts = X509_STORE_new();
14625 if (cts) {
14626 for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
14627 auto itmp = sk_X509_INFO_value(inf, i);
14628 if (!itmp) { continue; }
14629
14630 if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); }
14631 if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); }
14632 }
14633 }
14634
14635 sk_X509_INFO_pop_free(inf, X509_INFO_free);
14636 return cts;
14637}
14638
14639inline void ClientImpl::set_server_certificate_verifier(
14640 std::function<SSLVerifierResponse(SSL *ssl)> /*verifier*/) {
14641 // Base implementation does nothing - SSLClient overrides this
14642}
14643#endif
14644
14645inline void ClientImpl::set_logger(Logger logger) {
14646 logger_ = std::move(logger);
14647}
14648
14650 error_logger_ = std::move(error_logger);
14651}
14652
14653/*
14654 * SSL/TLS Common Implementation
14655 */
14656
14658#ifdef CPPHTTPLIB_SSL_ENABLED
14659 if (session) {
14660 tls::shutdown(session, true);
14661 tls::free_session(session);
14662 session = nullptr;
14663 }
14664#endif
14665
14666 if (sock != INVALID_SOCKET) {
14669 }
14670}
14671
14672// Universal client implementation
14673inline Client::Client(const std::string &scheme_host_port)
14674 : Client(scheme_host_port, std::string(), std::string()) {}
14675
14676inline Client::Client(const std::string &scheme_host_port,
14677 const std::string &client_cert_path,
14678 const std::string &client_key_path) {
14679 const static std::regex re(
14680 R"((?:([a-z]+):\/\/)?(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)");
14681
14682 std::smatch m;
14683 if (std::regex_match(scheme_host_port, m, re)) {
14684 auto scheme = m[1].str();
14685
14686#ifdef CPPHTTPLIB_SSL_ENABLED
14687 if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
14688#else
14689 if (!scheme.empty() && scheme != "http") {
14690#endif
14691#ifndef CPPHTTPLIB_NO_EXCEPTIONS
14692 std::string msg = "'" + scheme + "' scheme is not supported.";
14693 throw std::invalid_argument(msg);
14694#endif
14695 return;
14696 }
14697
14698 auto is_ssl = scheme == "https";
14699
14700 auto host = m[2].str();
14701 if (host.empty()) { host = m[3].str(); }
14702
14703 auto port_str = m[4].str();
14704 auto port = is_ssl ? 443 : 80;
14705 if (!port_str.empty() && !detail::parse_port(port_str, port)) { return; }
14706
14707 if (is_ssl) {
14708#ifdef CPPHTTPLIB_SSL_ENABLED
14709 cli_ = detail::make_unique<SSLClient>(host, port, client_cert_path,
14710 client_key_path);
14711 is_ssl_ = is_ssl;
14712#endif
14713 } else {
14714 cli_ = detail::make_unique<ClientImpl>(host, port, client_cert_path,
14715 client_key_path);
14716 }
14717 } else {
14718 // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress)
14719 // if port param below changes.
14720 cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
14721 client_cert_path, client_key_path);
14722 }
14723} // namespace detail
14724
14725inline Client::Client(const std::string &host, int port)
14726 : cli_(detail::make_unique<ClientImpl>(host, port)) {}
14727
14728inline Client::Client(const std::string &host, int port,
14729 const std::string &client_cert_path,
14730 const std::string &client_key_path)
14731 : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
14732 client_key_path)) {}
14733
14734inline Client::~Client() = default;
14735
14736inline bool Client::is_valid() const {
14737 return cli_ != nullptr && cli_->is_valid();
14738}
14739
14740inline Result Client::Get(const std::string &path, DownloadProgress progress) {
14741 return cli_->Get(path, std::move(progress));
14742}
14743inline Result Client::Get(const std::string &path, const Headers &headers,
14744 DownloadProgress progress) {
14745 return cli_->Get(path, headers, std::move(progress));
14746}
14747inline Result Client::Get(const std::string &path,
14748 ContentReceiver content_receiver,
14749 DownloadProgress progress) {
14750 return cli_->Get(path, std::move(content_receiver), std::move(progress));
14751}
14752inline Result Client::Get(const std::string &path, const Headers &headers,
14753 ContentReceiver content_receiver,
14754 DownloadProgress progress) {
14755 return cli_->Get(path, headers, std::move(content_receiver),
14756 std::move(progress));
14757}
14758inline Result Client::Get(const std::string &path,
14759 ResponseHandler response_handler,
14760 ContentReceiver content_receiver,
14761 DownloadProgress progress) {
14762 return cli_->Get(path, std::move(response_handler),
14763 std::move(content_receiver), std::move(progress));
14764}
14765inline Result Client::Get(const std::string &path, const Headers &headers,
14766 ResponseHandler response_handler,
14767 ContentReceiver content_receiver,
14768 DownloadProgress progress) {
14769 return cli_->Get(path, headers, std::move(response_handler),
14770 std::move(content_receiver), std::move(progress));
14771}
14772inline Result Client::Get(const std::string &path, const Params &params,
14773 const Headers &headers, DownloadProgress progress) {
14774 return cli_->Get(path, params, headers, std::move(progress));
14775}
14776inline Result Client::Get(const std::string &path, const Params &params,
14777 const Headers &headers,
14778 ContentReceiver content_receiver,
14779 DownloadProgress progress) {
14780 return cli_->Get(path, params, headers, std::move(content_receiver),
14781 std::move(progress));
14782}
14783inline Result Client::Get(const std::string &path, const Params &params,
14784 const Headers &headers,
14785 ResponseHandler response_handler,
14786 ContentReceiver content_receiver,
14787 DownloadProgress progress) {
14788 return cli_->Get(path, params, headers, std::move(response_handler),
14789 std::move(content_receiver), std::move(progress));
14790}
14791
14792inline Result Client::Head(const std::string &path) { return cli_->Head(path); }
14793inline Result Client::Head(const std::string &path, const Headers &headers) {
14794 return cli_->Head(path, headers);
14795}
14796
14797inline Result Client::Post(const std::string &path) { return cli_->Post(path); }
14798inline Result Client::Post(const std::string &path, const Headers &headers) {
14799 return cli_->Post(path, headers);
14800}
14801inline Result Client::Post(const std::string &path, const char *body,
14802 size_t content_length,
14803 const std::string &content_type,
14804 UploadProgress progress) {
14805 return cli_->Post(path, body, content_length, content_type, progress);
14806}
14807inline Result Client::Post(const std::string &path, const Headers &headers,
14808 const char *body, size_t content_length,
14809 const std::string &content_type,
14810 UploadProgress progress) {
14811 return cli_->Post(path, headers, body, content_length, content_type,
14812 progress);
14813}
14814inline Result Client::Post(const std::string &path, const std::string &body,
14815 const std::string &content_type,
14816 UploadProgress progress) {
14817 return cli_->Post(path, body, content_type, progress);
14818}
14819inline Result Client::Post(const std::string &path, const Headers &headers,
14820 const std::string &body,
14821 const std::string &content_type,
14822 UploadProgress progress) {
14823 return cli_->Post(path, headers, body, content_type, progress);
14824}
14825inline Result Client::Post(const std::string &path, size_t content_length,
14826 ContentProvider content_provider,
14827 const std::string &content_type,
14828 UploadProgress progress) {
14829 return cli_->Post(path, content_length, std::move(content_provider),
14830 content_type, progress);
14831}
14832inline Result Client::Post(const std::string &path, size_t content_length,
14833 ContentProvider content_provider,
14834 const std::string &content_type,
14835 ContentReceiver content_receiver,
14836 UploadProgress progress) {
14837 return cli_->Post(path, content_length, std::move(content_provider),
14838 content_type, std::move(content_receiver), progress);
14839}
14840inline Result Client::Post(const std::string &path,
14841 ContentProviderWithoutLength content_provider,
14842 const std::string &content_type,
14843 UploadProgress progress) {
14844 return cli_->Post(path, std::move(content_provider), content_type, progress);
14845}
14846inline Result Client::Post(const std::string &path,
14847 ContentProviderWithoutLength content_provider,
14848 const std::string &content_type,
14849 ContentReceiver content_receiver,
14850 UploadProgress progress) {
14851 return cli_->Post(path, std::move(content_provider), content_type,
14852 std::move(content_receiver), progress);
14853}
14854inline Result Client::Post(const std::string &path, const Headers &headers,
14855 size_t content_length,
14856 ContentProvider content_provider,
14857 const std::string &content_type,
14858 UploadProgress progress) {
14859 return cli_->Post(path, headers, content_length, std::move(content_provider),
14860 content_type, progress);
14861}
14862inline Result Client::Post(const std::string &path, const Headers &headers,
14863 size_t content_length,
14864 ContentProvider content_provider,
14865 const std::string &content_type,
14866 ContentReceiver content_receiver,
14867 DownloadProgress progress) {
14868 return cli_->Post(path, headers, content_length, std::move(content_provider),
14869 content_type, std::move(content_receiver), progress);
14870}
14871inline Result Client::Post(const std::string &path, const Headers &headers,
14872 ContentProviderWithoutLength content_provider,
14873 const std::string &content_type,
14874 UploadProgress progress) {
14875 return cli_->Post(path, headers, std::move(content_provider), content_type,
14876 progress);
14877}
14878inline Result Client::Post(const std::string &path, const Headers &headers,
14879 ContentProviderWithoutLength content_provider,
14880 const std::string &content_type,
14881 ContentReceiver content_receiver,
14882 DownloadProgress progress) {
14883 return cli_->Post(path, headers, std::move(content_provider), content_type,
14884 std::move(content_receiver), progress);
14885}
14886inline Result Client::Post(const std::string &path, const Params &params) {
14887 return cli_->Post(path, params);
14888}
14889inline Result Client::Post(const std::string &path, const Headers &headers,
14890 const Params &params) {
14891 return cli_->Post(path, headers, params);
14892}
14893inline Result Client::Post(const std::string &path,
14894 const UploadFormDataItems &items,
14895 UploadProgress progress) {
14896 return cli_->Post(path, items, progress);
14897}
14898inline Result Client::Post(const std::string &path, const Headers &headers,
14899 const UploadFormDataItems &items,
14900 UploadProgress progress) {
14901 return cli_->Post(path, headers, items, progress);
14902}
14903inline Result Client::Post(const std::string &path, const Headers &headers,
14904 const UploadFormDataItems &items,
14905 const std::string &boundary,
14906 UploadProgress progress) {
14907 return cli_->Post(path, headers, items, boundary, progress);
14908}
14909inline Result Client::Post(const std::string &path, const Headers &headers,
14910 const UploadFormDataItems &items,
14911 const FormDataProviderItems &provider_items,
14912 UploadProgress progress) {
14913 return cli_->Post(path, headers, items, provider_items, progress);
14914}
14915inline Result Client::Post(const std::string &path, const Headers &headers,
14916 const std::string &body,
14917 const std::string &content_type,
14918 ContentReceiver content_receiver,
14919 DownloadProgress progress) {
14920 return cli_->Post(path, headers, body, content_type,
14921 std::move(content_receiver), progress);
14922}
14923
14924inline Result Client::Put(const std::string &path) { return cli_->Put(path); }
14925inline Result Client::Put(const std::string &path, const Headers &headers) {
14926 return cli_->Put(path, headers);
14927}
14928inline Result Client::Put(const std::string &path, const char *body,
14929 size_t content_length,
14930 const std::string &content_type,
14931 UploadProgress progress) {
14932 return cli_->Put(path, body, content_length, content_type, progress);
14933}
14934inline Result Client::Put(const std::string &path, const Headers &headers,
14935 const char *body, size_t content_length,
14936 const std::string &content_type,
14937 UploadProgress progress) {
14938 return cli_->Put(path, headers, body, content_length, content_type, progress);
14939}
14940inline Result Client::Put(const std::string &path, const std::string &body,
14941 const std::string &content_type,
14942 UploadProgress progress) {
14943 return cli_->Put(path, body, content_type, progress);
14944}
14945inline Result Client::Put(const std::string &path, const Headers &headers,
14946 const std::string &body,
14947 const std::string &content_type,
14948 UploadProgress progress) {
14949 return cli_->Put(path, headers, body, content_type, progress);
14950}
14951inline Result Client::Put(const std::string &path, size_t content_length,
14952 ContentProvider content_provider,
14953 const std::string &content_type,
14954 UploadProgress progress) {
14955 return cli_->Put(path, content_length, std::move(content_provider),
14956 content_type, progress);
14957}
14958inline Result Client::Put(const std::string &path, size_t content_length,
14959 ContentProvider content_provider,
14960 const std::string &content_type,
14961 ContentReceiver content_receiver,
14962 UploadProgress progress) {
14963 return cli_->Put(path, content_length, std::move(content_provider),
14964 content_type, std::move(content_receiver), progress);
14965}
14966inline Result Client::Put(const std::string &path,
14967 ContentProviderWithoutLength content_provider,
14968 const std::string &content_type,
14969 UploadProgress progress) {
14970 return cli_->Put(path, std::move(content_provider), content_type, progress);
14971}
14972inline Result Client::Put(const std::string &path,
14973 ContentProviderWithoutLength content_provider,
14974 const std::string &content_type,
14975 ContentReceiver content_receiver,
14976 UploadProgress progress) {
14977 return cli_->Put(path, std::move(content_provider), content_type,
14978 std::move(content_receiver), progress);
14979}
14980inline Result Client::Put(const std::string &path, const Headers &headers,
14981 size_t content_length,
14982 ContentProvider content_provider,
14983 const std::string &content_type,
14984 UploadProgress progress) {
14985 return cli_->Put(path, headers, content_length, std::move(content_provider),
14986 content_type, progress);
14987}
14988inline Result Client::Put(const std::string &path, const Headers &headers,
14989 size_t content_length,
14990 ContentProvider content_provider,
14991 const std::string &content_type,
14992 ContentReceiver content_receiver,
14993 UploadProgress progress) {
14994 return cli_->Put(path, headers, content_length, std::move(content_provider),
14995 content_type, std::move(content_receiver), progress);
14996}
14997inline Result Client::Put(const std::string &path, const Headers &headers,
14998 ContentProviderWithoutLength content_provider,
14999 const std::string &content_type,
15000 UploadProgress progress) {
15001 return cli_->Put(path, headers, std::move(content_provider), content_type,
15002 progress);
15003}
15004inline Result Client::Put(const std::string &path, const Headers &headers,
15005 ContentProviderWithoutLength content_provider,
15006 const std::string &content_type,
15007 ContentReceiver content_receiver,
15008 UploadProgress progress) {
15009 return cli_->Put(path, headers, std::move(content_provider), content_type,
15010 std::move(content_receiver), progress);
15011}
15012inline Result Client::Put(const std::string &path, const Params &params) {
15013 return cli_->Put(path, params);
15014}
15015inline Result Client::Put(const std::string &path, const Headers &headers,
15016 const Params &params) {
15017 return cli_->Put(path, headers, params);
15018}
15019inline Result Client::Put(const std::string &path,
15020 const UploadFormDataItems &items,
15021 UploadProgress progress) {
15022 return cli_->Put(path, items, progress);
15023}
15024inline Result Client::Put(const std::string &path, const Headers &headers,
15025 const UploadFormDataItems &items,
15026 UploadProgress progress) {
15027 return cli_->Put(path, headers, items, progress);
15028}
15029inline Result Client::Put(const std::string &path, const Headers &headers,
15030 const UploadFormDataItems &items,
15031 const std::string &boundary,
15032 UploadProgress progress) {
15033 return cli_->Put(path, headers, items, boundary, progress);
15034}
15035inline Result Client::Put(const std::string &path, const Headers &headers,
15036 const UploadFormDataItems &items,
15037 const FormDataProviderItems &provider_items,
15038 UploadProgress progress) {
15039 return cli_->Put(path, headers, items, provider_items, progress);
15040}
15041inline Result Client::Put(const std::string &path, const Headers &headers,
15042 const std::string &body,
15043 const std::string &content_type,
15044 ContentReceiver content_receiver,
15045 DownloadProgress progress) {
15046 return cli_->Put(path, headers, body, content_type, content_receiver,
15047 progress);
15048}
15049
15050inline Result Client::Patch(const std::string &path) {
15051 return cli_->Patch(path);
15052}
15053inline Result Client::Patch(const std::string &path, const Headers &headers) {
15054 return cli_->Patch(path, headers);
15055}
15056inline Result Client::Patch(const std::string &path, const char *body,
15057 size_t content_length,
15058 const std::string &content_type,
15059 UploadProgress progress) {
15060 return cli_->Patch(path, body, content_length, content_type, progress);
15061}
15062inline Result Client::Patch(const std::string &path, const Headers &headers,
15063 const char *body, size_t content_length,
15064 const std::string &content_type,
15065 UploadProgress progress) {
15066 return cli_->Patch(path, headers, body, content_length, content_type,
15067 progress);
15068}
15069inline Result Client::Patch(const std::string &path, const std::string &body,
15070 const std::string &content_type,
15071 UploadProgress progress) {
15072 return cli_->Patch(path, body, content_type, progress);
15073}
15074inline Result Client::Patch(const std::string &path, const Headers &headers,
15075 const std::string &body,
15076 const std::string &content_type,
15077 UploadProgress progress) {
15078 return cli_->Patch(path, headers, body, content_type, progress);
15079}
15080inline Result Client::Patch(const std::string &path, size_t content_length,
15081 ContentProvider content_provider,
15082 const std::string &content_type,
15083 UploadProgress progress) {
15084 return cli_->Patch(path, content_length, std::move(content_provider),
15085 content_type, progress);
15086}
15087inline Result Client::Patch(const std::string &path, size_t content_length,
15088 ContentProvider content_provider,
15089 const std::string &content_type,
15090 ContentReceiver content_receiver,
15091 UploadProgress progress) {
15092 return cli_->Patch(path, content_length, std::move(content_provider),
15093 content_type, std::move(content_receiver), progress);
15094}
15095inline Result Client::Patch(const std::string &path,
15096 ContentProviderWithoutLength content_provider,
15097 const std::string &content_type,
15098 UploadProgress progress) {
15099 return cli_->Patch(path, std::move(content_provider), content_type, progress);
15100}
15101inline Result Client::Patch(const std::string &path,
15102 ContentProviderWithoutLength content_provider,
15103 const std::string &content_type,
15104 ContentReceiver content_receiver,
15105 UploadProgress progress) {
15106 return cli_->Patch(path, std::move(content_provider), content_type,
15107 std::move(content_receiver), progress);
15108}
15109inline Result Client::Patch(const std::string &path, const Headers &headers,
15110 size_t content_length,
15111 ContentProvider content_provider,
15112 const std::string &content_type,
15113 UploadProgress progress) {
15114 return cli_->Patch(path, headers, content_length, std::move(content_provider),
15115 content_type, progress);
15116}
15117inline Result Client::Patch(const std::string &path, const Headers &headers,
15118 size_t content_length,
15119 ContentProvider content_provider,
15120 const std::string &content_type,
15121 ContentReceiver content_receiver,
15122 UploadProgress progress) {
15123 return cli_->Patch(path, headers, content_length, std::move(content_provider),
15124 content_type, std::move(content_receiver), progress);
15125}
15126inline Result Client::Patch(const std::string &path, const Headers &headers,
15127 ContentProviderWithoutLength content_provider,
15128 const std::string &content_type,
15129 UploadProgress progress) {
15130 return cli_->Patch(path, headers, std::move(content_provider), content_type,
15131 progress);
15132}
15133inline Result Client::Patch(const std::string &path, const Headers &headers,
15134 ContentProviderWithoutLength content_provider,
15135 const std::string &content_type,
15136 ContentReceiver content_receiver,
15137 UploadProgress progress) {
15138 return cli_->Patch(path, headers, std::move(content_provider), content_type,
15139 std::move(content_receiver), progress);
15140}
15141inline Result Client::Patch(const std::string &path, const Params &params) {
15142 return cli_->Patch(path, params);
15143}
15144inline Result Client::Patch(const std::string &path, const Headers &headers,
15145 const Params &params) {
15146 return cli_->Patch(path, headers, params);
15147}
15148inline Result Client::Patch(const std::string &path,
15149 const UploadFormDataItems &items,
15150 UploadProgress progress) {
15151 return cli_->Patch(path, items, progress);
15152}
15153inline Result Client::Patch(const std::string &path, const Headers &headers,
15154 const UploadFormDataItems &items,
15155 UploadProgress progress) {
15156 return cli_->Patch(path, headers, items, progress);
15157}
15158inline Result Client::Patch(const std::string &path, const Headers &headers,
15159 const UploadFormDataItems &items,
15160 const std::string &boundary,
15161 UploadProgress progress) {
15162 return cli_->Patch(path, headers, items, boundary, progress);
15163}
15164inline Result Client::Patch(const std::string &path, const Headers &headers,
15165 const UploadFormDataItems &items,
15166 const FormDataProviderItems &provider_items,
15167 UploadProgress progress) {
15168 return cli_->Patch(path, headers, items, provider_items, progress);
15169}
15170inline Result Client::Patch(const std::string &path, const Headers &headers,
15171 const std::string &body,
15172 const std::string &content_type,
15173 ContentReceiver content_receiver,
15174 DownloadProgress progress) {
15175 return cli_->Patch(path, headers, body, content_type, content_receiver,
15176 progress);
15177}
15178
15179inline Result Client::Delete(const std::string &path,
15180 DownloadProgress progress) {
15181 return cli_->Delete(path, progress);
15182}
15183inline Result Client::Delete(const std::string &path, const Headers &headers,
15184 DownloadProgress progress) {
15185 return cli_->Delete(path, headers, progress);
15186}
15187inline Result Client::Delete(const std::string &path, const char *body,
15188 size_t content_length,
15189 const std::string &content_type,
15190 DownloadProgress progress) {
15191 return cli_->Delete(path, body, content_length, content_type, progress);
15192}
15193inline Result Client::Delete(const std::string &path, const Headers &headers,
15194 const char *body, size_t content_length,
15195 const std::string &content_type,
15196 DownloadProgress progress) {
15197 return cli_->Delete(path, headers, body, content_length, content_type,
15198 progress);
15199}
15200inline Result Client::Delete(const std::string &path, const std::string &body,
15201 const std::string &content_type,
15202 DownloadProgress progress) {
15203 return cli_->Delete(path, body, content_type, progress);
15204}
15205inline Result Client::Delete(const std::string &path, const Headers &headers,
15206 const std::string &body,
15207 const std::string &content_type,
15208 DownloadProgress progress) {
15209 return cli_->Delete(path, headers, body, content_type, progress);
15210}
15211inline Result Client::Delete(const std::string &path, const Params &params,
15212 DownloadProgress progress) {
15213 return cli_->Delete(path, params, progress);
15214}
15215inline Result Client::Delete(const std::string &path, const Headers &headers,
15216 const Params &params, DownloadProgress progress) {
15217 return cli_->Delete(path, headers, params, progress);
15218}
15219
15220inline Result Client::Options(const std::string &path) {
15221 return cli_->Options(path);
15222}
15223inline Result Client::Options(const std::string &path, const Headers &headers) {
15224 return cli_->Options(path, headers);
15225}
15226
15228Client::open_stream(const std::string &method, const std::string &path,
15229 const Params &params, const Headers &headers,
15230 const std::string &body, const std::string &content_type) {
15231 return cli_->open_stream(method, path, params, headers, body, content_type);
15232}
15233
15234inline bool Client::send(Request &req, Response &res, Error &error) {
15235 return cli_->send(req, res, error);
15236}
15237
15238inline Result Client::send(const Request &req) { return cli_->send(req); }
15239
15240inline void Client::stop() { cli_->stop(); }
15241
15242inline std::string Client::host() const { return cli_->host(); }
15243
15244inline int Client::port() const { return cli_->port(); }
15245
15246inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
15247
15248inline socket_t Client::socket() const { return cli_->socket(); }
15249
15250inline void
15251Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
15252 cli_->set_hostname_addr_map(std::move(addr_map));
15253}
15254
15256 cli_->set_default_headers(std::move(headers));
15257}
15258
15260 std::function<ssize_t(Stream &, Headers &)> const &writer) {
15261 cli_->set_header_writer(writer);
15262}
15263
15264inline void Client::set_address_family(int family) {
15265 cli_->set_address_family(family);
15266}
15267
15268inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
15269
15270inline void Client::set_socket_options(SocketOptions socket_options) {
15271 cli_->set_socket_options(std::move(socket_options));
15272}
15273
15274inline void Client::set_connection_timeout(time_t sec, time_t usec) {
15275 cli_->set_connection_timeout(sec, usec);
15276}
15277
15278inline void Client::set_read_timeout(time_t sec, time_t usec) {
15279 cli_->set_read_timeout(sec, usec);
15280}
15281
15282inline void Client::set_write_timeout(time_t sec, time_t usec) {
15283 cli_->set_write_timeout(sec, usec);
15284}
15285
15286inline void Client::set_basic_auth(const std::string &username,
15287 const std::string &password) {
15288 cli_->set_basic_auth(username, password);
15289}
15290inline void Client::set_bearer_token_auth(const std::string &token) {
15291 cli_->set_bearer_token_auth(token);
15292}
15293
15294inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
15295inline void Client::set_follow_location(bool on) {
15296 cli_->set_follow_location(on);
15297}
15298
15299inline void Client::set_path_encode(bool on) { cli_->set_path_encode(on); }
15300
15301[[deprecated("Use set_path_encode() instead. "
15302 "This function will be removed by v1.0.0.")]]
15303inline void Client::set_url_encode(bool on) {
15304 cli_->set_path_encode(on);
15305}
15306
15307inline void Client::set_compress(bool on) { cli_->set_compress(on); }
15308
15309inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
15310
15311inline void Client::set_payload_max_length(size_t length) {
15312 cli_->set_payload_max_length(length);
15313}
15314
15315inline void Client::set_interface(const std::string &intf) {
15316 cli_->set_interface(intf);
15317}
15318
15319inline void Client::set_proxy(const std::string &host, int port) {
15320 cli_->set_proxy(host, port);
15321}
15322inline void Client::set_proxy_basic_auth(const std::string &username,
15323 const std::string &password) {
15324 cli_->set_proxy_basic_auth(username, password);
15325}
15326inline void Client::set_proxy_bearer_token_auth(const std::string &token) {
15327 cli_->set_proxy_bearer_token_auth(token);
15328}
15329
15330inline void Client::set_logger(Logger logger) {
15331 cli_->set_logger(std::move(logger));
15332}
15333
15334inline void Client::set_error_logger(ErrorLogger error_logger) {
15335 cli_->set_error_logger(std::move(error_logger));
15336}
15337
15338/*
15339 * Group 6: SSL Server and Client implementation
15340 */
15341
15342#ifdef CPPHTTPLIB_SSL_ENABLED
15343
15344// SSL HTTP server implementation
15345inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
15346 const char *client_ca_cert_file_path,
15347 const char *client_ca_cert_dir_path,
15348 const char *private_key_password) {
15349 using namespace tls;
15350
15351 ctx_ = create_server_context();
15352 if (!ctx_) { return; }
15353
15354 // Load server certificate and private key
15355 if (!set_server_cert_file(ctx_, cert_path, private_key_path,
15356 private_key_password)) {
15357 last_ssl_error_ = static_cast<int>(get_error());
15358 free_context(ctx_);
15359 ctx_ = nullptr;
15360 return;
15361 }
15362
15363 // Load client CA certificates for client authentication
15364 if (client_ca_cert_file_path || client_ca_cert_dir_path) {
15365 if (!set_client_ca_file(ctx_, client_ca_cert_file_path,
15366 client_ca_cert_dir_path)) {
15367 last_ssl_error_ = static_cast<int>(get_error());
15368 free_context(ctx_);
15369 ctx_ = nullptr;
15370 return;
15371 }
15372 // Enable client certificate verification
15373 set_verify_client(ctx_, true);
15374 }
15375}
15376
15377inline SSLServer::SSLServer(const PemMemory &pem) {
15378 using namespace tls;
15379 ctx_ = create_server_context();
15380 if (ctx_) {
15381 if (!set_server_cert_pem(ctx_, pem.cert_pem, pem.key_pem,
15382 pem.private_key_password)) {
15383 last_ssl_error_ = static_cast<int>(get_error());
15384 free_context(ctx_);
15385 ctx_ = nullptr;
15386 } else if (pem.client_ca_pem && pem.client_ca_pem_len > 0) {
15387 if (!load_ca_pem(ctx_, pem.client_ca_pem, pem.client_ca_pem_len)) {
15388 last_ssl_error_ = static_cast<int>(get_error());
15389 free_context(ctx_);
15390 ctx_ = nullptr;
15391 } else {
15392 set_verify_client(ctx_, true);
15393 }
15394 }
15395 }
15396}
15397
15398inline SSLServer::SSLServer(const tls::ContextSetupCallback &setup_callback) {
15399 using namespace tls;
15400 ctx_ = create_server_context();
15401 if (ctx_) {
15402 if (!setup_callback(ctx_)) {
15403 free_context(ctx_);
15404 ctx_ = nullptr;
15405 }
15406 }
15407}
15408
15409inline SSLServer::~SSLServer() {
15410 if (ctx_) { tls::free_context(ctx_); }
15411}
15412
15413inline bool SSLServer::is_valid() const { return ctx_ != nullptr; }
15414
15415inline bool SSLServer::process_and_close_socket(socket_t sock) {
15416 using namespace tls;
15417
15418 // Create TLS session with mutex protection
15419 session_t session = nullptr;
15420 {
15421 std::lock_guard<std::mutex> guard(ctx_mutex_);
15422 session = create_session(static_cast<ctx_t>(ctx_), sock);
15423 }
15424
15425 if (!session) {
15426 last_ssl_error_ = static_cast<int>(get_error());
15429 return false;
15430 }
15431
15432 // Use scope_exit to ensure cleanup on all paths (including exceptions)
15433 bool handshake_done = false;
15434 bool ret = false;
15435 bool websocket_upgraded = false;
15436 auto cleanup = detail::scope_exit([&] {
15437 if (handshake_done) { shutdown(session, !websocket_upgraded && ret); }
15438 free_session(session);
15441 });
15442
15443 // Perform TLS accept handshake with timeout
15444 TlsError tls_err;
15445 if (!accept_nonblocking(session, sock, read_timeout_sec_, read_timeout_usec_,
15446 &tls_err)) {
15447#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
15448 // Map TlsError to legacy ssl_error for backward compatibility
15449 if (tls_err.code == ErrorCode::WantRead) {
15450 last_ssl_error_ = SSL_ERROR_WANT_READ;
15451 } else if (tls_err.code == ErrorCode::WantWrite) {
15452 last_ssl_error_ = SSL_ERROR_WANT_WRITE;
15453 } else {
15454 last_ssl_error_ = SSL_ERROR_SSL;
15455 }
15456#else
15457 last_ssl_error_ = static_cast<int>(get_error());
15458#endif
15459 return false;
15460 }
15461
15462 handshake_done = true;
15463
15464 std::string remote_addr;
15465 int remote_port = 0;
15466 detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
15467
15468 std::string local_addr;
15469 int local_port = 0;
15470 detail::get_local_ip_and_port(sock, local_addr, local_port);
15471
15472 ret = detail::process_server_socket_ssl(
15473 svr_sock_, session, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
15474 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
15475 write_timeout_usec_,
15476 [&](Stream &strm, bool close_connection, bool &connection_closed) {
15477 return process_request(
15478 strm, remote_addr, remote_port, local_addr, local_port,
15479 close_connection, connection_closed,
15480 [&](Request &req) { req.ssl = session; }, &websocket_upgraded);
15481 });
15482
15483 return ret;
15484}
15485
15486inline bool SSLServer::update_certs_pem(const char *cert_pem,
15487 const char *key_pem,
15488 const char *client_ca_pem,
15489 const char *password) {
15490 if (!ctx_) { return false; }
15491 std::lock_guard<std::mutex> guard(ctx_mutex_);
15492 if (!tls::update_server_cert(ctx_, cert_pem, key_pem, password)) {
15493 return false;
15494 }
15495 if (client_ca_pem) {
15496 return tls::update_server_client_ca(ctx_, client_ca_pem);
15497 }
15498 return true;
15499}
15500
15501// SSL HTTP client implementation
15502inline SSLClient::~SSLClient() {
15503 if (ctx_) { tls::free_context(ctx_); }
15504 // Make sure to shut down SSL since shutdown_ssl will resolve to the
15505 // base function rather than the derived function once we get to the
15506 // base class destructor, and won't free the SSL (causing a leak).
15507 shutdown_ssl_impl(socket_, true);
15508}
15509
15510inline bool SSLClient::is_valid() const { return ctx_ != nullptr; }
15511
15512inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
15513 shutdown_ssl_impl(socket, shutdown_gracefully);
15514}
15515
15516inline void SSLClient::shutdown_ssl_impl(Socket &socket,
15517 bool shutdown_gracefully) {
15518 if (socket.sock == INVALID_SOCKET) {
15519 assert(socket.ssl == nullptr);
15520 return;
15521 }
15522 if (socket.ssl) {
15523 tls::shutdown(socket.ssl, shutdown_gracefully);
15524 {
15525 std::lock_guard<std::mutex> guard(ctx_mutex_);
15526 tls::free_session(socket.ssl);
15527 }
15528 socket.ssl = nullptr;
15529 }
15530 assert(socket.ssl == nullptr);
15531}
15532
15533inline bool SSLClient::process_socket(
15534 const Socket &socket,
15535 std::chrono::time_point<std::chrono::steady_clock> start_time,
15536 std::function<bool(Stream &strm)> callback) {
15537 assert(socket.ssl);
15538 return detail::process_client_socket_ssl(
15539 socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
15540 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time,
15541 std::move(callback));
15542}
15543
15544inline bool SSLClient::is_ssl() const { return true; }
15545
15546inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
15547 if (!is_valid()) {
15548 error = Error::SSLConnection;
15549 return false;
15550 }
15551 return ClientImpl::create_and_connect_socket(socket, error);
15552}
15553
15554inline bool SSLClient::setup_proxy_connection(
15555 Socket &socket,
15556 std::chrono::time_point<std::chrono::steady_clock> start_time,
15557 Response &res, bool &success, Error &error) {
15558 if (proxy_host_.empty() || proxy_port_ == -1) { return true; }
15559
15560 if (!connect_with_proxy(socket, start_time, res, success, error)) {
15561 return false;
15562 }
15563
15564 if (!initialize_ssl(socket, error)) {
15565 success = false;
15566 return false;
15567 }
15568
15569 return true;
15570}
15571
15572// Assumes that socket_mutex_ is locked and that there are no requests in
15573// flight
15574inline bool SSLClient::connect_with_proxy(
15575 Socket &socket,
15576 std::chrono::time_point<std::chrono::steady_clock> start_time,
15577 Response &res, bool &success, Error &error) {
15578 success = true;
15579 Response proxy_res;
15581 socket.sock, read_timeout_sec_, read_timeout_usec_,
15582 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
15583 start_time, [&](Stream &strm) {
15584 Request req2;
15585 req2.method = "CONNECT";
15586 req2.path =
15587 detail::make_host_and_port_string_always_port(host_, port_);
15588 if (max_timeout_msec_ > 0) {
15589 req2.start_time_ = std::chrono::steady_clock::now();
15590 }
15591 return process_request(strm, req2, proxy_res, false, error);
15592 })) {
15593 // Thread-safe to close everything because we are assuming there are no
15594 // requests in flight
15595 shutdown_ssl(socket, true);
15596 shutdown_socket(socket);
15597 close_socket(socket);
15598 success = false;
15599 return false;
15600 }
15601
15602 if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) {
15603 if (!proxy_digest_auth_username_.empty() &&
15604 !proxy_digest_auth_password_.empty()) {
15605 std::map<std::string, std::string> auth;
15606 if (detail::parse_www_authenticate(proxy_res, auth, true)) {
15607 // Close the current socket and create a new one for the authenticated
15608 // request
15609 shutdown_ssl(socket, true);
15610 shutdown_socket(socket);
15611 close_socket(socket);
15612
15613 // Create a new socket for the authenticated CONNECT request
15614 if (!ensure_socket_connection(socket, error)) {
15615 success = false;
15616 output_error_log(error, nullptr);
15617 return false;
15618 }
15619
15620 proxy_res = Response();
15622 socket.sock, read_timeout_sec_, read_timeout_usec_,
15623 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
15624 start_time, [&](Stream &strm) {
15625 Request req3;
15626 req3.method = "CONNECT";
15627 req3.path = detail::make_host_and_port_string_always_port(
15628 host_, port_);
15629 req3.headers.insert(detail::make_digest_authentication_header(
15630 req3, auth, 1, detail::random_string(10),
15631 proxy_digest_auth_username_, proxy_digest_auth_password_,
15632 true));
15633 if (max_timeout_msec_ > 0) {
15634 req3.start_time_ = std::chrono::steady_clock::now();
15635 }
15636 return process_request(strm, req3, proxy_res, false, error);
15637 })) {
15638 // Thread-safe to close everything because we are assuming there are
15639 // no requests in flight
15640 shutdown_ssl(socket, true);
15641 shutdown_socket(socket);
15642 close_socket(socket);
15643 success = false;
15644 return false;
15645 }
15646 }
15647 }
15648 }
15649
15650 // If status code is not 200, proxy request is failed.
15651 // Set error to ProxyConnection and return proxy response
15652 // as the response of the request
15653 if (proxy_res.status != StatusCode::OK_200) {
15654 error = Error::ProxyConnection;
15655 output_error_log(error, nullptr);
15656 res = std::move(proxy_res);
15657 // Thread-safe to close everything because we are assuming there are
15658 // no requests in flight
15659 shutdown_ssl(socket, true);
15660 shutdown_socket(socket);
15661 close_socket(socket);
15662 return false;
15663 }
15664
15665 return true;
15666}
15667
15668inline bool SSLClient::ensure_socket_connection(Socket &socket, Error &error) {
15669 if (!ClientImpl::ensure_socket_connection(socket, error)) { return false; }
15670
15671 if (!proxy_host_.empty() && proxy_port_ != -1) { return true; }
15672
15673 if (!initialize_ssl(socket, error)) {
15674 shutdown_socket(socket);
15675 close_socket(socket);
15676 return false;
15677 }
15678
15679 return true;
15680}
15681
15682// SSL HTTP client implementation
15683inline SSLClient::SSLClient(const std::string &host)
15684 : SSLClient(host, 443, std::string(), std::string()) {}
15685
15686inline SSLClient::SSLClient(const std::string &host, int port)
15687 : SSLClient(host, port, std::string(), std::string()) {}
15688
15689inline SSLClient::SSLClient(const std::string &host, int port,
15690 const std::string &client_cert_path,
15691 const std::string &client_key_path,
15692 const std::string &private_key_password)
15693 : ClientImpl(host, port, client_cert_path, client_key_path) {
15694 ctx_ = tls::create_client_context();
15695 if (!ctx_) { return; }
15696
15697 tls::set_min_version(ctx_, tls::Version::TLS1_2);
15698
15699 if (!client_cert_path.empty() && !client_key_path.empty()) {
15700 const char *password =
15701 private_key_password.empty() ? nullptr : private_key_password.c_str();
15702 if (!tls::set_client_cert_file(ctx_, client_cert_path.c_str(),
15703 client_key_path.c_str(), password)) {
15704 last_backend_error_ = tls::get_error();
15705 tls::free_context(ctx_);
15706 ctx_ = nullptr;
15707 }
15708 }
15709}
15710
15711inline SSLClient::SSLClient(const std::string &host, int port,
15712 const PemMemory &pem)
15713 : ClientImpl(host, port) {
15714 ctx_ = tls::create_client_context();
15715 if (!ctx_) { return; }
15716
15717 tls::set_min_version(ctx_, tls::Version::TLS1_2);
15718
15719 if (pem.cert_pem && pem.key_pem) {
15720 if (!tls::set_client_cert_pem(ctx_, pem.cert_pem, pem.key_pem,
15721 pem.private_key_password)) {
15722 last_backend_error_ = tls::get_error();
15723 tls::free_context(ctx_);
15724 ctx_ = nullptr;
15725 }
15726 }
15727}
15728
15729inline void SSLClient::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
15730 if (ca_cert_store && ctx_) {
15731 // set_ca_store takes ownership of ca_cert_store
15732 tls::set_ca_store(ctx_, ca_cert_store);
15733 } else if (ca_cert_store) {
15734 tls::free_ca_store(ca_cert_store);
15735 }
15736}
15737
15738inline void
15739SSLClient::set_server_certificate_verifier(tls::VerifyCallback verifier) {
15740 if (!ctx_) { return; }
15741 tls::set_verify_callback(ctx_, verifier);
15742}
15743
15744inline void SSLClient::set_session_verifier(
15745 std::function<SSLVerifierResponse(tls::session_t)> verifier) {
15746 session_verifier_ = std::move(verifier);
15747}
15748
15749#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
15750inline void SSLClient::enable_windows_certificate_verification(bool enabled) {
15751 enable_windows_cert_verification_ = enabled;
15752}
15753#endif
15754
15755inline void SSLClient::load_ca_cert_store(const char *ca_cert,
15756 std::size_t size) {
15757 if (ctx_ && ca_cert && size > 0) {
15758 ca_cert_pem_.assign(ca_cert, size); // Store for redirect transfer
15759 tls::load_ca_pem(ctx_, ca_cert, size);
15760 }
15761}
15762
15763inline bool SSLClient::load_certs() {
15764 auto ret = true;
15765
15766 std::call_once(initialize_cert_, [&]() {
15767 std::lock_guard<std::mutex> guard(ctx_mutex_);
15768
15769 if (!ca_cert_file_path_.empty()) {
15770 if (!tls::load_ca_file(ctx_, ca_cert_file_path_.c_str())) {
15771 last_backend_error_ = tls::get_error();
15772 ret = false;
15773 }
15774 } else if (!ca_cert_dir_path_.empty()) {
15775 if (!tls::load_ca_dir(ctx_, ca_cert_dir_path_.c_str())) {
15776 last_backend_error_ = tls::get_error();
15777 ret = false;
15778 }
15779 } else if (ca_cert_pem_.empty()) {
15780 if (!tls::load_system_certs(ctx_)) {
15781 last_backend_error_ = tls::get_error();
15782 }
15783 }
15784 });
15785
15786 return ret;
15787}
15788
15789inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
15790 using namespace tls;
15791
15792 // Load CA certificates if server verification is enabled
15793 if (server_certificate_verification_) {
15794 if (!load_certs()) {
15795 error = Error::SSLLoadingCerts;
15796 output_error_log(error, nullptr);
15797 return false;
15798 }
15799 }
15800
15801 bool is_ip = detail::is_ip_address(host_);
15802
15803#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
15804 // MbedTLS/wolfSSL need explicit verification mode (OpenSSL uses
15805 // SSL_VERIFY_NONE by default and performs all verification post-handshake).
15806 // For IP addresses with verification enabled, use OPTIONAL mode since
15807 // these backends require hostname for strict verification.
15808 if (is_ip && server_certificate_verification_) {
15809 set_verify_client(ctx_, false);
15810 } else {
15811 set_verify_client(ctx_, server_certificate_verification_);
15812 }
15813#endif
15814
15815 // Create TLS session
15816 session_t session = nullptr;
15817 {
15818 std::lock_guard<std::mutex> guard(ctx_mutex_);
15819 session = create_session(ctx_, socket.sock);
15820 }
15821
15822 if (!session) {
15823 error = Error::SSLConnection;
15824 last_backend_error_ = get_error();
15825 return false;
15826 }
15827
15828 // Use scope_exit to ensure session is freed on error paths
15829 bool success = false;
15830 auto session_guard = detail::scope_exit([&] {
15831 if (!success) { free_session(session); }
15832 });
15833
15834 // Set SNI extension (skip for IP addresses per RFC 6066).
15835 // On MbedTLS, set_sni also enables hostname verification internally.
15836 // On OpenSSL, set_sni only sets SNI; verification is done post-handshake.
15837 if (!is_ip) {
15838 if (!set_sni(session, host_.c_str())) {
15839 error = Error::SSLConnection;
15840 last_backend_error_ = get_error();
15841 return false;
15842 }
15843 }
15844
15845 // Perform non-blocking TLS handshake with timeout
15846 TlsError tls_err;
15847 if (!connect_nonblocking(session, socket.sock, connection_timeout_sec_,
15848 connection_timeout_usec_, &tls_err)) {
15849 last_ssl_error_ = static_cast<int>(tls_err.code);
15850 last_backend_error_ = tls_err.backend_code;
15851 if (tls_err.code == ErrorCode::CertVerifyFailed) {
15853 } else if (tls_err.code == ErrorCode::HostnameMismatch) {
15855 } else {
15856 error = Error::SSLConnection;
15857 }
15858 output_error_log(error, nullptr);
15859 return false;
15860 }
15861
15862 // Post-handshake session verifier callback
15863 auto verification_status = SSLVerifierResponse::NoDecisionMade;
15864 if (session_verifier_) { verification_status = session_verifier_(session); }
15865
15866 if (verification_status == SSLVerifierResponse::CertificateRejected) {
15867 last_backend_error_ = get_error();
15869 output_error_log(error, nullptr);
15870 return false;
15871 }
15872
15873 // Default server certificate verification
15874 if (verification_status == SSLVerifierResponse::NoDecisionMade &&
15875 server_certificate_verification_) {
15876 verify_result_ = tls::get_verify_result(session);
15877 if (verify_result_ != 0) {
15878 last_backend_error_ = static_cast<uint64_t>(verify_result_);
15880 output_error_log(error, nullptr);
15881 return false;
15882 }
15883
15884 auto server_cert = get_peer_cert(session);
15885 if (!server_cert) {
15886 last_backend_error_ = get_error();
15888 output_error_log(error, nullptr);
15889 return false;
15890 }
15891 auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
15892
15893 // Hostname verification (post-handshake for all cases).
15894 // On OpenSSL, verification is always post-handshake (SSL_VERIFY_NONE).
15895 // On MbedTLS, set_sni already enabled hostname verification during
15896 // handshake for non-IP hosts, but this check is still needed for IP
15897 // addresses where SNI is not set.
15898 if (server_hostname_verification_) {
15899 if (!verify_hostname(server_cert, host_.c_str())) {
15900 last_backend_error_ = hostname_mismatch_code();
15902 output_error_log(error, nullptr);
15903 return false;
15904 }
15905 }
15906
15907#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
15908 // Additional Windows Schannel verification.
15909 // This provides real-time certificate validation with Windows Update
15910 // integration, working with both OpenSSL and MbedTLS backends.
15911 // Skip when a custom CA cert is specified, as the Windows certificate
15912 // store would not know about user-provided CA certificates.
15913 if (enable_windows_cert_verification_ && ca_cert_file_path_.empty() &&
15914 ca_cert_dir_path_.empty() && ca_cert_pem_.empty()) {
15915 std::vector<unsigned char> der;
15916 if (get_cert_der(server_cert, der)) {
15917 uint64_t wincrypt_error = 0;
15918 if (!detail::verify_cert_with_windows_schannel(
15919 der, host_, server_hostname_verification_, wincrypt_error)) {
15920 last_backend_error_ = wincrypt_error;
15922 output_error_log(error, nullptr);
15923 return false;
15924 }
15925 }
15926 }
15927#endif
15928 }
15929
15930 success = true;
15931 socket.ssl = session;
15932 return true;
15933}
15934
15935inline void Client::set_digest_auth(const std::string &username,
15936 const std::string &password) {
15937 cli_->set_digest_auth(username, password);
15938}
15939
15940inline void Client::set_proxy_digest_auth(const std::string &username,
15941 const std::string &password) {
15942 cli_->set_proxy_digest_auth(username, password);
15943}
15944
15945inline void Client::enable_server_certificate_verification(bool enabled) {
15946 cli_->enable_server_certificate_verification(enabled);
15947}
15948
15949inline void Client::enable_server_hostname_verification(bool enabled) {
15950 cli_->enable_server_hostname_verification(enabled);
15951}
15952
15953#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
15954inline void Client::enable_windows_certificate_verification(bool enabled) {
15955 if (is_ssl_) {
15956 static_cast<SSLClient &>(*cli_).enable_windows_certificate_verification(
15957 enabled);
15958 }
15959}
15960#endif
15961
15962inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
15963 const std::string &ca_cert_dir_path) {
15964 cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
15965}
15966
15967inline void Client::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
15968 if (is_ssl_) {
15969 static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
15970 } else if (ca_cert_store) {
15971 tls::free_ca_store(ca_cert_store);
15972 }
15973}
15974
15975inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) {
15976 set_ca_cert_store(tls::create_ca_store(ca_cert, size));
15977}
15978
15979inline void
15980Client::set_server_certificate_verifier(tls::VerifyCallback verifier) {
15981 if (is_ssl_) {
15982 static_cast<SSLClient &>(*cli_).set_server_certificate_verifier(
15983 std::move(verifier));
15984 }
15985}
15986
15987inline void Client::set_session_verifier(
15988 std::function<SSLVerifierResponse(tls::session_t)> verifier) {
15989 if (is_ssl_) {
15990 static_cast<SSLClient &>(*cli_).set_session_verifier(std::move(verifier));
15991 }
15992}
15993
15994inline tls::ctx_t Client::tls_context() const {
15995 if (is_ssl_) { return static_cast<SSLClient &>(*cli_).tls_context(); }
15996 return nullptr;
15997}
15998
15999#endif // CPPHTTPLIB_SSL_ENABLED
16000
16001/*
16002 * Group 7: TLS abstraction layer - Common API
16003 */
16004
16005#ifdef CPPHTTPLIB_SSL_ENABLED
16006
16007namespace tls {
16008
16009// Helper for PeerCert construction
16010inline PeerCert get_peer_cert_from_session(const_session_t session) {
16011 return PeerCert(get_peer_cert(session));
16012}
16013
16014namespace impl {
16015
16016inline VerifyCallback &get_verify_callback() {
16017 static thread_local VerifyCallback callback;
16018 return callback;
16019}
16020
16021inline VerifyCallback &get_mbedtls_verify_callback() {
16022 static thread_local VerifyCallback callback;
16023 return callback;
16024}
16025
16026// Check if a string is an IPv4 address
16027inline bool is_ipv4_address(const std::string &str) {
16028 int dots = 0;
16029 for (char c : str) {
16030 if (c == '.') {
16031 dots++;
16032 } else if (!isdigit(static_cast<unsigned char>(c))) {
16033 return false;
16034 }
16035 }
16036 return dots == 3;
16037}
16038
16039// Parse IPv4 address string to bytes
16040inline bool parse_ipv4(const std::string &str, unsigned char *out) {
16041 const char *p = str.c_str();
16042 for (int i = 0; i < 4; i++) {
16043 if (i > 0) {
16044 if (*p != '.') { return false; }
16045 p++;
16046 }
16047 int val = 0;
16048 int digits = 0;
16049 while (*p >= '0' && *p <= '9') {
16050 val = val * 10 + (*p - '0');
16051 if (val > 255) { return false; }
16052 p++;
16053 digits++;
16054 }
16055 if (digits == 0) { return false; }
16056 // Reject leading zeros (e.g., "01.002.03.04") to prevent ambiguity
16057 if (digits > 1 && *(p - digits) == '0') { return false; }
16058 out[i] = static_cast<unsigned char>(val);
16059 }
16060 return *p == '\0';
16061}
16062
16063#ifdef _WIN32
16064// Enumerate Windows system certificates and call callback with DER data
16065template <typename Callback>
16066inline bool enumerate_windows_system_certs(Callback cb) {
16067 bool loaded = false;
16068 static const wchar_t *store_names[] = {L"ROOT", L"CA"};
16069 for (auto store_name : store_names) {
16070 HCERTSTORE hStore = CertOpenSystemStoreW(0, store_name);
16071 if (hStore) {
16072 PCCERT_CONTEXT pContext = nullptr;
16073 while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
16074 nullptr) {
16075 if (cb(pContext->pbCertEncoded, pContext->cbCertEncoded)) {
16076 loaded = true;
16077 }
16078 }
16079 CertCloseStore(hStore, 0);
16080 }
16081 }
16082 return loaded;
16083}
16084#endif
16085
16086#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
16087// Enumerate macOS Keychain certificates and call callback with DER data
16088template <typename Callback>
16089inline bool enumerate_macos_keychain_certs(Callback cb) {
16090 bool loaded = false;
16091 CFArrayRef certs = nullptr;
16092 OSStatus status = SecTrustCopyAnchorCertificates(&certs);
16093 if (status == errSecSuccess && certs) {
16094 CFIndex count = CFArrayGetCount(certs);
16095 for (CFIndex i = 0; i < count; i++) {
16096 SecCertificateRef cert =
16097 (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
16098 CFDataRef data = SecCertificateCopyData(cert);
16099 if (data) {
16100 if (cb(CFDataGetBytePtr(data),
16101 static_cast<size_t>(CFDataGetLength(data)))) {
16102 loaded = true;
16103 }
16104 CFRelease(data);
16105 }
16106 }
16107 CFRelease(certs);
16108 }
16109 return loaded;
16110}
16111#endif
16112
16113#if !defined(_WIN32) && !(defined(__APPLE__) && \
16114 defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN))
16115// Common CA certificate file paths on Linux/Unix
16116inline const char **system_ca_paths() {
16117 static const char *paths[] = {
16118 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu
16119 "/etc/pki/tls/certs/ca-bundle.crt", // RHEL/CentOS
16120 "/etc/ssl/ca-bundle.pem", // OpenSUSE
16121 "/etc/pki/tls/cacert.pem", // OpenELEC
16122 "/etc/ssl/cert.pem", // Alpine, FreeBSD
16123 nullptr};
16124 return paths;
16125}
16126
16127// Common CA certificate directory paths on Linux/Unix
16128inline const char **system_ca_dirs() {
16129 static const char *dirs[] = {"/etc/ssl/certs", // Debian/Ubuntu
16130 "/etc/pki/tls/certs", // RHEL/CentOS
16131 "/usr/share/ca-certificates", // Other
16132 nullptr};
16133 return dirs;
16134}
16135#endif
16136
16137} // namespace impl
16138
16139inline bool set_client_ca_file(ctx_t ctx, const char *ca_file,
16140 const char *ca_dir) {
16141 if (!ctx) { return false; }
16142
16143 bool success = true;
16144 if (ca_file && *ca_file) {
16145 if (!load_ca_file(ctx, ca_file)) { success = false; }
16146 }
16147 if (ca_dir && *ca_dir) {
16148 if (!load_ca_dir(ctx, ca_dir)) { success = false; }
16149 }
16150
16151#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
16152 // Set CA list for client certificate request (CertificateRequest message)
16153 if (ca_file && *ca_file) {
16154 auto list = SSL_load_client_CA_file(ca_file);
16155 if (list) { SSL_CTX_set_client_CA_list(static_cast<SSL_CTX *>(ctx), list); }
16156 }
16157#endif
16158
16159 return success;
16160}
16161
16162inline bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key,
16163 const char *password) {
16164 return set_client_cert_pem(ctx, cert, key, password);
16165}
16166
16167inline bool set_server_cert_file(ctx_t ctx, const char *cert_path,
16168 const char *key_path, const char *password) {
16169 return set_client_cert_file(ctx, cert_path, key_path, password);
16170}
16171
16172// PeerCert implementation
16173inline PeerCert::PeerCert() = default;
16174
16175inline PeerCert::PeerCert(cert_t cert) : cert_(cert) {}
16176
16177inline PeerCert::PeerCert(PeerCert &&other) noexcept : cert_(other.cert_) {
16178 other.cert_ = nullptr;
16179}
16180
16181inline PeerCert &PeerCert::operator=(PeerCert &&other) noexcept {
16182 if (this != &other) {
16183 if (cert_) { free_cert(cert_); }
16184 cert_ = other.cert_;
16185 other.cert_ = nullptr;
16186 }
16187 return *this;
16188}
16189
16190inline PeerCert::~PeerCert() {
16191 if (cert_) { free_cert(cert_); }
16192}
16193
16194inline PeerCert::operator bool() const { return cert_ != nullptr; }
16195
16196inline std::string PeerCert::subject_cn() const {
16197 return cert_ ? get_cert_subject_cn(cert_) : std::string();
16198}
16199
16200inline std::string PeerCert::issuer_name() const {
16201 return cert_ ? get_cert_issuer_name(cert_) : std::string();
16202}
16203
16204inline bool PeerCert::check_hostname(const char *hostname) const {
16205 return cert_ ? verify_hostname(cert_, hostname) : false;
16206}
16207
16208inline std::vector<SanEntry> PeerCert::sans() const {
16209 std::vector<SanEntry> result;
16210 if (cert_) { get_cert_sans(cert_, result); }
16211 return result;
16212}
16213
16214inline bool PeerCert::validity(time_t &not_before, time_t &not_after) const {
16215 return cert_ ? get_cert_validity(cert_, not_before, not_after) : false;
16216}
16217
16218inline std::string PeerCert::serial() const {
16219 return cert_ ? get_cert_serial(cert_) : std::string();
16220}
16221
16222// VerifyContext method implementations
16223inline std::string VerifyContext::subject_cn() const {
16224 return cert ? get_cert_subject_cn(cert) : std::string();
16225}
16226
16227inline std::string VerifyContext::issuer_name() const {
16228 return cert ? get_cert_issuer_name(cert) : std::string();
16229}
16230
16231inline bool VerifyContext::check_hostname(const char *hostname) const {
16232 return cert ? verify_hostname(cert, hostname) : false;
16233}
16234
16235inline std::vector<SanEntry> VerifyContext::sans() const {
16236 std::vector<SanEntry> result;
16237 if (cert) { get_cert_sans(cert, result); }
16238 return result;
16239}
16240
16241inline bool VerifyContext::validity(time_t &not_before,
16242 time_t &not_after) const {
16243 return cert ? get_cert_validity(cert, not_before, not_after) : false;
16244}
16245
16246inline std::string VerifyContext::serial() const {
16247 return cert ? get_cert_serial(cert) : std::string();
16248}
16249
16250// TlsError static method implementation
16251inline std::string TlsError::verify_error_to_string(long error_code) {
16252 return verify_error_string(error_code);
16253}
16254
16255} // namespace tls
16256
16257// Request::peer_cert() implementation
16258inline tls::PeerCert Request::peer_cert() const {
16259 return tls::get_peer_cert_from_session(ssl);
16260}
16261
16262// Request::sni() implementation
16263inline std::string Request::sni() const {
16264 if (!ssl) { return std::string(); }
16265 const char *s = tls::get_sni(ssl);
16266 return s ? std::string(s) : std::string();
16267}
16268
16269#endif // CPPHTTPLIB_SSL_ENABLED
16270
16271/*
16272 * Group 8: TLS abstraction layer - OpenSSL backend
16273 */
16274
16275#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
16276inline SSL_CTX *Client::ssl_context() const {
16277 if (is_ssl_) { return static_cast<SSLClient &>(*cli_).ssl_context(); }
16278 return nullptr;
16279}
16280
16281inline void Client::set_server_certificate_verifier(
16282 std::function<SSLVerifierResponse(SSL *ssl)> verifier) {
16283 cli_->set_server_certificate_verifier(verifier);
16284}
16285
16286inline long Client::get_verify_result() const {
16287 if (is_ssl_) { return static_cast<SSLClient &>(*cli_).get_verify_result(); }
16288 return -1; // NOTE: -1 doesn't match any of X509_V_ERR_???
16289}
16290#endif // CPPHTTPLIB_OPENSSL_SUPPORT
16291
16292/*
16293 * OpenSSL Backend Implementation
16294 */
16295
16296#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
16297namespace tls {
16298
16299namespace impl {
16300
16301// OpenSSL-specific helpers for converting native types to PEM
16302inline std::string x509_to_pem(X509 *cert) {
16303 if (!cert) return {};
16304 BIO *bio = BIO_new(BIO_s_mem());
16305 if (!bio) return {};
16306 if (PEM_write_bio_X509(bio, cert) != 1) {
16307 BIO_free(bio);
16308 return {};
16309 }
16310 char *data = nullptr;
16311 long len = BIO_get_mem_data(bio, &data);
16312 std::string pem(data, static_cast<size_t>(len));
16313 BIO_free(bio);
16314 return pem;
16315}
16316
16317inline std::string evp_pkey_to_pem(EVP_PKEY *key) {
16318 if (!key) return {};
16319 BIO *bio = BIO_new(BIO_s_mem());
16320 if (!bio) return {};
16321 if (PEM_write_bio_PrivateKey(bio, key, nullptr, nullptr, 0, nullptr,
16322 nullptr) != 1) {
16323 BIO_free(bio);
16324 return {};
16325 }
16326 char *data = nullptr;
16327 long len = BIO_get_mem_data(bio, &data);
16328 std::string pem(data, static_cast<size_t>(len));
16329 BIO_free(bio);
16330 return pem;
16331}
16332
16333inline std::string x509_store_to_pem(X509_STORE *store) {
16334 if (!store) return {};
16335 std::string pem;
16336 auto objs = X509_STORE_get0_objects(store);
16337 if (!objs) return {};
16338 auto count = sk_X509_OBJECT_num(objs);
16339 for (decltype(count) i = 0; i < count; i++) {
16340 auto obj = sk_X509_OBJECT_value(objs, i);
16341 if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
16342 auto cert = X509_OBJECT_get0_X509(obj);
16343 if (cert) { pem += x509_to_pem(cert); }
16344 }
16345 }
16346 return pem;
16347}
16348
16349// Helper to map OpenSSL SSL_get_error to ErrorCode
16350inline ErrorCode map_ssl_error(int ssl_error, int &out_errno) {
16351 switch (ssl_error) {
16352 case SSL_ERROR_NONE: return ErrorCode::Success;
16353 case SSL_ERROR_WANT_READ: return ErrorCode::WantRead;
16354 case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite;
16355 case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed;
16356 case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError;
16357 case SSL_ERROR_SSL:
16358 default: return ErrorCode::Fatal;
16359 }
16360}
16361
16362// Helper: Create client CA list from PEM string
16363// Returns a new STACK_OF(X509_NAME)* or nullptr on failure
16364// Caller takes ownership of returned list
16365inline STACK_OF(X509_NAME) *
16366 create_client_ca_list_from_pem(const char *ca_pem) {
16367 if (!ca_pem) { return nullptr; }
16368
16369 auto ca_list = sk_X509_NAME_new_null();
16370 if (!ca_list) { return nullptr; }
16371
16372 BIO *bio = BIO_new_mem_buf(ca_pem, -1);
16373 if (!bio) {
16374 sk_X509_NAME_pop_free(ca_list, X509_NAME_free);
16375 return nullptr;
16376 }
16377
16378 X509 *cert = nullptr;
16379 while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
16380 nullptr) {
16381 X509_NAME *name = X509_get_subject_name(cert);
16382 if (name) { sk_X509_NAME_push(ca_list, X509_NAME_dup(name)); }
16383 X509_free(cert);
16384 }
16385 BIO_free(bio);
16386
16387 return ca_list;
16388}
16389
16390// Helper: Extract CA names from X509_STORE
16391// Returns a new STACK_OF(X509_NAME)* or nullptr on failure
16392// Caller takes ownership of returned list
16393inline STACK_OF(X509_NAME) *
16394 extract_client_ca_list_from_store(X509_STORE *store) {
16395 if (!store) { return nullptr; }
16396
16397 auto ca_list = sk_X509_NAME_new_null();
16398 if (!ca_list) { return nullptr; }
16399
16400 auto objs = X509_STORE_get0_objects(store);
16401 if (!objs) {
16402 sk_X509_NAME_free(ca_list);
16403 return nullptr;
16404 }
16405
16406 auto count = sk_X509_OBJECT_num(objs);
16407 for (decltype(count) i = 0; i < count; i++) {
16408 auto obj = sk_X509_OBJECT_value(objs, i);
16409 if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
16410 auto cert = X509_OBJECT_get0_X509(obj);
16411 if (cert) {
16412 auto subject = X509_get_subject_name(cert);
16413 if (subject) {
16414 auto name_dup = X509_NAME_dup(subject);
16415 if (name_dup) { sk_X509_NAME_push(ca_list, name_dup); }
16416 }
16417 }
16418 }
16419 }
16420
16421 if (sk_X509_NAME_num(ca_list) == 0) {
16422 sk_X509_NAME_free(ca_list);
16423 return nullptr;
16424 }
16425
16426 return ca_list;
16427}
16428
16429// OpenSSL verify callback wrapper
16430inline int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
16431 auto &callback = get_verify_callback();
16432 if (!callback) { return preverify_ok; }
16433
16434 // Get SSL object from X509_STORE_CTX
16435 auto ssl = static_cast<SSL *>(
16436 X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
16437 if (!ssl) { return preverify_ok; }
16438
16439 // Get current certificate and depth
16440 auto cert = X509_STORE_CTX_get_current_cert(ctx);
16441 int depth = X509_STORE_CTX_get_error_depth(ctx);
16442 int error = X509_STORE_CTX_get_error(ctx);
16443
16444 // Build context
16445 VerifyContext verify_ctx;
16446 verify_ctx.session = static_cast<session_t>(ssl);
16447 verify_ctx.cert = static_cast<cert_t>(cert);
16448 verify_ctx.depth = depth;
16449 verify_ctx.preverify_ok = (preverify_ok != 0);
16450 verify_ctx.error_code = error;
16451 verify_ctx.error_string =
16452 (error != X509_V_OK) ? X509_verify_cert_error_string(error) : nullptr;
16453
16454 return callback(verify_ctx) ? 1 : 0;
16455}
16456
16457} // namespace impl
16458
16459inline ctx_t create_client_context() {
16460 SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
16461 if (ctx) {
16462 // Disable auto-retry to properly handle non-blocking I/O
16463 SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY);
16464 // Set minimum TLS version
16465 SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
16466 }
16467 return static_cast<ctx_t>(ctx);
16468}
16469
16470inline void free_context(ctx_t ctx) {
16471 if (ctx) { SSL_CTX_free(static_cast<SSL_CTX *>(ctx)); }
16472}
16473
16474inline bool set_min_version(ctx_t ctx, Version version) {
16475 if (!ctx) return false;
16476 return SSL_CTX_set_min_proto_version(static_cast<SSL_CTX *>(ctx),
16477 static_cast<int>(version)) == 1;
16478}
16479
16480inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
16481 if (!ctx || !pem || len == 0) return false;
16482
16483 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16484 auto store = SSL_CTX_get_cert_store(ssl_ctx);
16485 if (!store) return false;
16486
16487 auto bio = BIO_new_mem_buf(pem, static_cast<int>(len));
16488 if (!bio) return false;
16489
16490 bool ok = true;
16491 X509 *cert = nullptr;
16492 while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
16493 nullptr) {
16494 if (X509_STORE_add_cert(store, cert) != 1) {
16495 // Ignore duplicate errors
16496 auto err = ERR_peek_last_error();
16497 if (ERR_GET_REASON(err) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
16498 ok = false;
16499 }
16500 }
16501 X509_free(cert);
16502 if (!ok) break;
16503 }
16504 BIO_free(bio);
16505
16506 // Clear any "no more certificates" errors
16507 ERR_clear_error();
16508 return ok;
16509}
16510
16511inline bool load_ca_file(ctx_t ctx, const char *file_path) {
16512 if (!ctx || !file_path) return false;
16513 return SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ctx), file_path,
16514 nullptr) == 1;
16515}
16516
16517inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
16518 if (!ctx || !dir_path) return false;
16519 return SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ctx), nullptr,
16520 dir_path) == 1;
16521}
16522
16523inline bool load_system_certs(ctx_t ctx) {
16524 if (!ctx) return false;
16525 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16526
16527#ifdef _WIN32
16528 // Windows: Load from system certificate store (ROOT and CA)
16529 auto store = SSL_CTX_get_cert_store(ssl_ctx);
16530 if (!store) return false;
16531
16532 bool loaded_any = false;
16533 static const wchar_t *store_names[] = {L"ROOT", L"CA"};
16534 for (auto store_name : store_names) {
16535 auto hStore = CertOpenSystemStoreW(NULL, store_name);
16536 if (!hStore) continue;
16537
16538 PCCERT_CONTEXT pContext = nullptr;
16539 while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
16540 nullptr) {
16541 const unsigned char *data = pContext->pbCertEncoded;
16542 auto x509 = d2i_X509(nullptr, &data, pContext->cbCertEncoded);
16543 if (x509) {
16544 if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
16545 X509_free(x509);
16546 }
16547 }
16548 CertCloseStore(hStore, 0);
16549 }
16550 return loaded_any;
16551
16552#elif defined(__APPLE__)
16553#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
16554 // macOS: Load from Keychain
16555 auto store = SSL_CTX_get_cert_store(ssl_ctx);
16556 if (!store) return false;
16557
16558 CFArrayRef certs = nullptr;
16559 if (SecTrustCopyAnchorCertificates(&certs) != errSecSuccess || !certs) {
16560 return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
16561 }
16562
16563 bool loaded_any = false;
16564 auto count = CFArrayGetCount(certs);
16565 for (CFIndex i = 0; i < count; i++) {
16566 auto cert = reinterpret_cast<SecCertificateRef>(
16567 const_cast<void *>(CFArrayGetValueAtIndex(certs, i)));
16568 CFDataRef der = SecCertificateCopyData(cert);
16569 if (der) {
16570 const unsigned char *data = CFDataGetBytePtr(der);
16571 auto x509 = d2i_X509(nullptr, &data, CFDataGetLength(der));
16572 if (x509) {
16573 if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
16574 X509_free(x509);
16575 }
16576 CFRelease(der);
16577 }
16578 }
16579 CFRelease(certs);
16580 return loaded_any || SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
16581#else
16582 return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
16583#endif
16584
16585#else
16586 // Other Unix: use default verify paths
16587 return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
16588#endif
16589}
16590
16591inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
16592 const char *password) {
16593 if (!ctx || !cert || !key) return false;
16594
16595 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16596
16597 // Load certificate
16598 auto cert_bio = BIO_new_mem_buf(cert, -1);
16599 if (!cert_bio) return false;
16600
16601 auto x509 = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
16602 BIO_free(cert_bio);
16603 if (!x509) return false;
16604
16605 auto cert_ok = SSL_CTX_use_certificate(ssl_ctx, x509) == 1;
16606 X509_free(x509);
16607 if (!cert_ok) return false;
16608
16609 // Load private key
16610 auto key_bio = BIO_new_mem_buf(key, -1);
16611 if (!key_bio) return false;
16612
16613 auto pkey = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr,
16614 password ? const_cast<char *>(password)
16615 : nullptr);
16616 BIO_free(key_bio);
16617 if (!pkey) return false;
16618
16619 auto key_ok = SSL_CTX_use_PrivateKey(ssl_ctx, pkey) == 1;
16620 EVP_PKEY_free(pkey);
16621
16622 return key_ok && SSL_CTX_check_private_key(ssl_ctx) == 1;
16623}
16624
16625inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
16626 const char *key_path, const char *password) {
16627 if (!ctx || !cert_path || !key_path) return false;
16628
16629 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16630
16631 if (password && password[0] != '\0') {
16632 SSL_CTX_set_default_passwd_cb_userdata(
16633 ssl_ctx, reinterpret_cast<void *>(const_cast<char *>(password)));
16634 }
16635
16636 return SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_path) == 1 &&
16637 SSL_CTX_use_PrivateKey_file(ssl_ctx, key_path, SSL_FILETYPE_PEM) == 1;
16638}
16639
16640inline ctx_t create_server_context() {
16641 SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
16642 if (ctx) {
16643 SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION |
16644 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
16645 SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
16646 }
16647 return static_cast<ctx_t>(ctx);
16648}
16649
16650inline void set_verify_client(ctx_t ctx, bool require) {
16651 if (!ctx) return;
16652 SSL_CTX_set_verify(static_cast<SSL_CTX *>(ctx),
16653 require
16654 ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
16655 : SSL_VERIFY_NONE,
16656 nullptr);
16657}
16658
16659inline session_t create_session(ctx_t ctx, socket_t sock) {
16660 if (!ctx || sock == INVALID_SOCKET) return nullptr;
16661
16662 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16663 SSL *ssl = SSL_new(ssl_ctx);
16664 if (!ssl) return nullptr;
16665
16666 // Disable auto-retry for proper non-blocking I/O handling
16667 SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
16668
16669 auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
16670 if (!bio) {
16671 SSL_free(ssl);
16672 return nullptr;
16673 }
16674
16675 SSL_set_bio(ssl, bio, bio);
16676 return static_cast<session_t>(ssl);
16677}
16678
16679inline void free_session(session_t session) {
16680 if (session) { SSL_free(static_cast<SSL *>(session)); }
16681}
16682
16683inline bool set_sni(session_t session, const char *hostname) {
16684 if (!session || !hostname) return false;
16685
16686 auto ssl = static_cast<SSL *>(session);
16687
16688 // Set SNI (Server Name Indication) only - does not enable verification
16689#if defined(OPENSSL_IS_BORINGSSL)
16690 return SSL_set_tlsext_host_name(ssl, hostname) == 1;
16691#else
16692 // Direct call instead of macro to suppress -Wold-style-cast warning
16693 return SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
16694 static_cast<void *>(const_cast<char *>(hostname))) == 1;
16695#endif
16696}
16697
16698inline bool set_hostname(session_t session, const char *hostname) {
16699 if (!session || !hostname) return false;
16700
16701 auto ssl = static_cast<SSL *>(session);
16702
16703 // Set SNI (Server Name Indication)
16704 if (!set_sni(session, hostname)) { return false; }
16705
16706 // Enable hostname verification
16707 auto param = SSL_get0_param(ssl);
16708 if (!param) return false;
16709
16710 X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
16711 if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { return false; }
16712
16713 SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
16714 return true;
16715}
16716
16717inline TlsError connect(session_t session) {
16718 if (!session) { return TlsError(); }
16719
16720 auto ssl = static_cast<SSL *>(session);
16721 auto ret = SSL_connect(ssl);
16722
16723 TlsError err;
16724 if (ret == 1) {
16725 err.code = ErrorCode::Success;
16726 } else {
16727 auto ssl_err = SSL_get_error(ssl, ret);
16728 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
16729 err.backend_code = ERR_get_error();
16730 }
16731 return err;
16732}
16733
16734inline TlsError accept(session_t session) {
16735 if (!session) { return TlsError(); }
16736
16737 auto ssl = static_cast<SSL *>(session);
16738 auto ret = SSL_accept(ssl);
16739
16740 TlsError err;
16741 if (ret == 1) {
16742 err.code = ErrorCode::Success;
16743 } else {
16744 auto ssl_err = SSL_get_error(ssl, ret);
16745 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
16746 err.backend_code = ERR_get_error();
16747 }
16748 return err;
16749}
16750
16751inline bool connect_nonblocking(session_t session, socket_t sock,
16752 time_t timeout_sec, time_t timeout_usec,
16753 TlsError *err) {
16754 if (!session) {
16755 if (err) { err->code = ErrorCode::Fatal; }
16756 return false;
16757 }
16758
16759 auto ssl = static_cast<SSL *>(session);
16760 auto bio = SSL_get_rbio(ssl);
16761
16762 // Set non-blocking mode for handshake
16763 detail::set_nonblocking(sock, true);
16764 if (bio) { BIO_set_nbio(bio, 1); }
16765
16766 auto cleanup = detail::scope_exit([&]() {
16767 // Restore blocking mode after handshake
16768 if (bio) { BIO_set_nbio(bio, 0); }
16769 detail::set_nonblocking(sock, false);
16770 });
16771
16772 auto res = 0;
16773 while ((res = SSL_connect(ssl)) != 1) {
16774 auto ssl_err = SSL_get_error(ssl, res);
16775 switch (ssl_err) {
16776 case SSL_ERROR_WANT_READ:
16777 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
16778 continue;
16779 }
16780 break;
16781 case SSL_ERROR_WANT_WRITE:
16782 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
16783 continue;
16784 }
16785 break;
16786 default: break;
16787 }
16788 if (err) {
16789 err->code = impl::map_ssl_error(ssl_err, err->sys_errno);
16790 err->backend_code = ERR_get_error();
16791 }
16792 return false;
16793 }
16794 if (err) { err->code = ErrorCode::Success; }
16795 return true;
16796}
16797
16798inline bool accept_nonblocking(session_t session, socket_t sock,
16799 time_t timeout_sec, time_t timeout_usec,
16800 TlsError *err) {
16801 if (!session) {
16802 if (err) { err->code = ErrorCode::Fatal; }
16803 return false;
16804 }
16805
16806 auto ssl = static_cast<SSL *>(session);
16807 auto bio = SSL_get_rbio(ssl);
16808
16809 // Set non-blocking mode for handshake
16810 detail::set_nonblocking(sock, true);
16811 if (bio) { BIO_set_nbio(bio, 1); }
16812
16813 auto cleanup = detail::scope_exit([&]() {
16814 // Restore blocking mode after handshake
16815 if (bio) { BIO_set_nbio(bio, 0); }
16816 detail::set_nonblocking(sock, false);
16817 });
16818
16819 auto res = 0;
16820 while ((res = SSL_accept(ssl)) != 1) {
16821 auto ssl_err = SSL_get_error(ssl, res);
16822 switch (ssl_err) {
16823 case SSL_ERROR_WANT_READ:
16824 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
16825 continue;
16826 }
16827 break;
16828 case SSL_ERROR_WANT_WRITE:
16829 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
16830 continue;
16831 }
16832 break;
16833 default: break;
16834 }
16835 if (err) {
16836 err->code = impl::map_ssl_error(ssl_err, err->sys_errno);
16837 err->backend_code = ERR_get_error();
16838 }
16839 return false;
16840 }
16841 if (err) { err->code = ErrorCode::Success; }
16842 return true;
16843}
16844
16845inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
16846 if (!session || !buf) {
16847 err.code = ErrorCode::Fatal;
16848 return -1;
16849 }
16850
16851 auto ssl = static_cast<SSL *>(session);
16852 constexpr auto max_len =
16853 static_cast<size_t>((std::numeric_limits<int>::max)());
16854 if (len > max_len) { len = max_len; }
16855 auto ret = SSL_read(ssl, buf, static_cast<int>(len));
16856
16857 if (ret > 0) {
16858 err.code = ErrorCode::Success;
16859 return ret;
16860 }
16861
16862 auto ssl_err = SSL_get_error(ssl, ret);
16863 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
16864 if (err.code == ErrorCode::Fatal) { err.backend_code = ERR_get_error(); }
16865 return -1;
16866}
16867
16868inline ssize_t write(session_t session, const void *buf, size_t len,
16869 TlsError &err) {
16870 if (!session || !buf) {
16871 err.code = ErrorCode::Fatal;
16872 return -1;
16873 }
16874
16875 auto ssl = static_cast<SSL *>(session);
16876 auto ret = SSL_write(ssl, buf, static_cast<int>(len));
16877
16878 if (ret > 0) {
16879 err.code = ErrorCode::Success;
16880 return ret;
16881 }
16882
16883 auto ssl_err = SSL_get_error(ssl, ret);
16884 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
16885 if (err.code == ErrorCode::Fatal) { err.backend_code = ERR_get_error(); }
16886 return -1;
16887}
16888
16889inline int pending(const_session_t session) {
16890 if (!session) return 0;
16891 return SSL_pending(static_cast<SSL *>(const_cast<void *>(session)));
16892}
16893
16894inline void shutdown(session_t session, bool graceful) {
16895 if (!session) return;
16896
16897 auto ssl = static_cast<SSL *>(session);
16898 if (graceful) {
16899 // First call sends close_notify
16900 if (SSL_shutdown(ssl) == 0) {
16901 // Second call waits for peer's close_notify
16902 SSL_shutdown(ssl);
16903 }
16904 }
16905}
16906
16907inline bool is_peer_closed(session_t session, socket_t sock) {
16908 if (!session) return true;
16909
16910 // Temporarily set socket to non-blocking to avoid blocking on SSL_peek
16911 detail::set_nonblocking(sock, true);
16912 auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
16913
16914 auto ssl = static_cast<SSL *>(session);
16915 char buf;
16916 auto ret = SSL_peek(ssl, &buf, 1);
16917 if (ret > 0) return false;
16918
16919 auto err = SSL_get_error(ssl, ret);
16920 return err == SSL_ERROR_ZERO_RETURN;
16921}
16922
16923inline cert_t get_peer_cert(const_session_t session) {
16924 if (!session) return nullptr;
16925 return static_cast<cert_t>(SSL_get1_peer_certificate(
16926 static_cast<SSL *>(const_cast<void *>(session))));
16927}
16928
16929inline void free_cert(cert_t cert) {
16930 if (cert) { X509_free(static_cast<X509 *>(cert)); }
16931}
16932
16933inline bool verify_hostname(cert_t cert, const char *hostname) {
16934 if (!cert || !hostname) return false;
16935
16936 auto x509 = static_cast<X509 *>(cert);
16937
16938 // Use X509_check_ip_asc for IP addresses, X509_check_host for DNS names
16939 if (detail::is_ip_address(hostname)) {
16940 return X509_check_ip_asc(x509, hostname, 0) == 1;
16941 }
16942 return X509_check_host(x509, hostname, strlen(hostname), 0, nullptr) == 1;
16943}
16944
16945inline uint64_t hostname_mismatch_code() {
16946 return static_cast<uint64_t>(X509_V_ERR_HOSTNAME_MISMATCH);
16947}
16948
16949inline long get_verify_result(const_session_t session) {
16950 if (!session) return X509_V_ERR_UNSPECIFIED;
16951 return SSL_get_verify_result(static_cast<SSL *>(const_cast<void *>(session)));
16952}
16953
16954inline std::string get_cert_subject_cn(cert_t cert) {
16955 if (!cert) return "";
16956 auto x509 = static_cast<X509 *>(cert);
16957 auto subject_name = X509_get_subject_name(x509);
16958 if (!subject_name) return "";
16959
16960 char buf[256];
16961 auto len =
16962 X509_NAME_get_text_by_NID(subject_name, NID_commonName, buf, sizeof(buf));
16963 if (len < 0) return "";
16964 return std::string(buf, static_cast<size_t>(len));
16965}
16966
16967inline std::string get_cert_issuer_name(cert_t cert) {
16968 if (!cert) return "";
16969 auto x509 = static_cast<X509 *>(cert);
16970 auto issuer_name = X509_get_issuer_name(x509);
16971 if (!issuer_name) return "";
16972
16973 char buf[256];
16974 X509_NAME_oneline(issuer_name, buf, sizeof(buf));
16975 return std::string(buf);
16976}
16977
16978inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
16979 sans.clear();
16980 if (!cert) return false;
16981 auto x509 = static_cast<X509 *>(cert);
16982
16983 auto names = static_cast<GENERAL_NAMES *>(
16984 X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
16985 if (!names) return true; // No SANs is valid
16986
16987 auto count = sk_GENERAL_NAME_num(names);
16988 for (decltype(count) i = 0; i < count; i++) {
16989 auto gen = sk_GENERAL_NAME_value(names, i);
16990 if (!gen) continue;
16991
16992 SanEntry entry;
16993 switch (gen->type) {
16994 case GEN_DNS:
16995 entry.type = SanType::DNS;
16996 if (gen->d.dNSName) {
16997 entry.value = std::string(
16998 reinterpret_cast<const char *>(
16999 ASN1_STRING_get0_data(gen->d.dNSName)),
17000 static_cast<size_t>(ASN1_STRING_length(gen->d.dNSName)));
17001 }
17002 break;
17003 case GEN_IPADD:
17004 entry.type = SanType::IP;
17005 if (gen->d.iPAddress) {
17006 auto data = ASN1_STRING_get0_data(gen->d.iPAddress);
17007 auto len = ASN1_STRING_length(gen->d.iPAddress);
17008 if (len == 4) {
17009 // IPv4
17010 char buf[INET_ADDRSTRLEN];
17011 inet_ntop(AF_INET, data, buf, sizeof(buf));
17012 entry.value = buf;
17013 } else if (len == 16) {
17014 // IPv6
17015 char buf[INET6_ADDRSTRLEN];
17016 inet_ntop(AF_INET6, data, buf, sizeof(buf));
17017 entry.value = buf;
17018 }
17019 }
17020 break;
17021 case GEN_EMAIL:
17022 entry.type = SanType::EMAIL;
17023 if (gen->d.rfc822Name) {
17024 entry.value = std::string(
17025 reinterpret_cast<const char *>(
17026 ASN1_STRING_get0_data(gen->d.rfc822Name)),
17027 static_cast<size_t>(ASN1_STRING_length(gen->d.rfc822Name)));
17028 }
17029 break;
17030 case GEN_URI:
17031 entry.type = SanType::URI;
17032 if (gen->d.uniformResourceIdentifier) {
17033 entry.value = std::string(
17034 reinterpret_cast<const char *>(
17035 ASN1_STRING_get0_data(gen->d.uniformResourceIdentifier)),
17036 static_cast<size_t>(
17037 ASN1_STRING_length(gen->d.uniformResourceIdentifier)));
17038 }
17039 break;
17040 default: entry.type = SanType::OTHER; break;
17041 }
17042
17043 if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
17044 }
17045
17046 GENERAL_NAMES_free(names);
17047 return true;
17048}
17049
17050inline bool get_cert_validity(cert_t cert, time_t &not_before,
17051 time_t &not_after) {
17052 if (!cert) return false;
17053 auto x509 = static_cast<X509 *>(cert);
17054
17055 auto nb = X509_get0_notBefore(x509);
17056 auto na = X509_get0_notAfter(x509);
17057 if (!nb || !na) return false;
17058
17059 ASN1_TIME *epoch = ASN1_TIME_new();
17060 if (!epoch) return false;
17061 auto se = detail::scope_exit([&] { ASN1_TIME_free(epoch); });
17062
17063 if (!ASN1_TIME_set(epoch, 0)) return false;
17064
17065 int pday, psec;
17066
17067 if (!ASN1_TIME_diff(&pday, &psec, epoch, nb)) return false;
17068 not_before = 86400 * (time_t)pday + psec;
17069
17070 if (!ASN1_TIME_diff(&pday, &psec, epoch, na)) return false;
17071 not_after = 86400 * (time_t)pday + psec;
17072
17073 return true;
17074}
17075
17076inline std::string get_cert_serial(cert_t cert) {
17077 if (!cert) return "";
17078 auto x509 = static_cast<X509 *>(cert);
17079
17080 auto serial = X509_get_serialNumber(x509);
17081 if (!serial) return "";
17082
17083 auto bn = ASN1_INTEGER_to_BN(serial, nullptr);
17084 if (!bn) return "";
17085
17086 auto hex = BN_bn2hex(bn);
17087 BN_free(bn);
17088 if (!hex) return "";
17089
17090 std::string result(hex);
17091 OPENSSL_free(hex);
17092 return result;
17093}
17094
17095inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
17096 if (!cert) return false;
17097 auto x509 = static_cast<X509 *>(cert);
17098 auto len = i2d_X509(x509, nullptr);
17099 if (len < 0) return false;
17100 der.resize(static_cast<size_t>(len));
17101 auto p = der.data();
17102 i2d_X509(x509, &p);
17103 return true;
17104}
17105
17106inline const char *get_sni(const_session_t session) {
17107 if (!session) return nullptr;
17108 auto ssl = static_cast<SSL *>(const_cast<void *>(session));
17109 return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
17110}
17111
17112inline uint64_t peek_error() { return ERR_peek_last_error(); }
17113
17114inline uint64_t get_error() { return ERR_get_error(); }
17115
17116inline std::string error_string(uint64_t code) {
17117 char buf[256];
17118 ERR_error_string_n(static_cast<unsigned long>(code), buf, sizeof(buf));
17119 return std::string(buf);
17120}
17121
17122inline ca_store_t create_ca_store(const char *pem, size_t len) {
17123 auto mem = BIO_new_mem_buf(pem, static_cast<int>(len));
17124 if (!mem) { return nullptr; }
17125 auto mem_guard = detail::scope_exit([&] { BIO_free_all(mem); });
17126
17127 auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
17128 if (!inf) { return nullptr; }
17129
17130 auto store = X509_STORE_new();
17131 if (store) {
17132 for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
17133 auto itmp = sk_X509_INFO_value(inf, i);
17134 if (!itmp) { continue; }
17135 if (itmp->x509) { X509_STORE_add_cert(store, itmp->x509); }
17136 if (itmp->crl) { X509_STORE_add_crl(store, itmp->crl); }
17137 }
17138 }
17139
17140 sk_X509_INFO_pop_free(inf, X509_INFO_free);
17141 return static_cast<ca_store_t>(store);
17142}
17143
17144inline void free_ca_store(ca_store_t store) {
17145 if (store) { X509_STORE_free(static_cast<X509_STORE *>(store)); }
17146}
17147
17148inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
17149 if (!ctx || !store) { return false; }
17150 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17151 auto x509_store = static_cast<X509_STORE *>(store);
17152
17153 // Check if same store is already set
17154 if (SSL_CTX_get_cert_store(ssl_ctx) == x509_store) { return true; }
17155
17156 // SSL_CTX_set_cert_store takes ownership and frees the old store
17157 SSL_CTX_set_cert_store(ssl_ctx, x509_store);
17158 return true;
17159}
17160
17161inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
17162 certs.clear();
17163 if (!ctx) { return 0; }
17164 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17165
17166 auto store = SSL_CTX_get_cert_store(ssl_ctx);
17167 if (!store) { return 0; }
17168
17169 auto objs = X509_STORE_get0_objects(store);
17170 if (!objs) { return 0; }
17171
17172 auto count = sk_X509_OBJECT_num(objs);
17173 for (decltype(count) i = 0; i < count; i++) {
17174 auto obj = sk_X509_OBJECT_value(objs, i);
17175 if (!obj) { continue; }
17176 if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
17177 auto x509 = X509_OBJECT_get0_X509(obj);
17178 if (x509) {
17179 // Increment reference count so caller can free it
17180 X509_up_ref(x509);
17181 certs.push_back(static_cast<cert_t>(x509));
17182 }
17183 }
17184 }
17185 return certs.size();
17186}
17187
17188inline std::vector<std::string> get_ca_names(ctx_t ctx) {
17189 std::vector<std::string> names;
17190 if (!ctx) { return names; }
17191 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17192
17193 auto store = SSL_CTX_get_cert_store(ssl_ctx);
17194 if (!store) { return names; }
17195
17196 auto objs = X509_STORE_get0_objects(store);
17197 if (!objs) { return names; }
17198
17199 auto count = sk_X509_OBJECT_num(objs);
17200 for (decltype(count) i = 0; i < count; i++) {
17201 auto obj = sk_X509_OBJECT_value(objs, i);
17202 if (!obj) { continue; }
17203 if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
17204 auto x509 = X509_OBJECT_get0_X509(obj);
17205 if (x509) {
17206 auto subject = X509_get_subject_name(x509);
17207 if (subject) {
17208 char buf[512];
17209 X509_NAME_oneline(subject, buf, sizeof(buf));
17210 names.push_back(buf);
17211 }
17212 }
17213 }
17214 }
17215 return names;
17216}
17217
17218inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
17219 const char *key_pem, const char *password) {
17220 if (!ctx || !cert_pem || !key_pem) { return false; }
17221 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17222
17223 // Load certificate from PEM
17224 auto cert_bio = BIO_new_mem_buf(cert_pem, -1);
17225 if (!cert_bio) { return false; }
17226 auto cert = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
17227 BIO_free(cert_bio);
17228 if (!cert) { return false; }
17229
17230 // Load private key from PEM
17231 auto key_bio = BIO_new_mem_buf(key_pem, -1);
17232 if (!key_bio) {
17233 X509_free(cert);
17234 return false;
17235 }
17236 auto key = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr,
17237 password ? const_cast<char *>(password)
17238 : nullptr);
17239 BIO_free(key_bio);
17240 if (!key) {
17241 X509_free(cert);
17242 return false;
17243 }
17244
17245 // Update certificate and key
17246 auto ret = SSL_CTX_use_certificate(ssl_ctx, cert) == 1 &&
17247 SSL_CTX_use_PrivateKey(ssl_ctx, key) == 1;
17248
17249 X509_free(cert);
17250 EVP_PKEY_free(key);
17251 return ret;
17252}
17253
17254inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
17255 if (!ctx || !ca_pem) { return false; }
17256 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17257
17258 // Create new X509_STORE from PEM
17259 auto store = create_ca_store(ca_pem, strlen(ca_pem));
17260 if (!store) { return false; }
17261
17262 // SSL_CTX_set_cert_store takes ownership
17263 SSL_CTX_set_cert_store(ssl_ctx, static_cast<X509_STORE *>(store));
17264
17265 // Set client CA list for client certificate request
17266 auto ca_list = impl::create_client_ca_list_from_pem(ca_pem);
17267 if (ca_list) {
17268 // SSL_CTX_set_client_CA_list takes ownership of ca_list
17269 SSL_CTX_set_client_CA_list(ssl_ctx, ca_list);
17270 }
17271
17272 return true;
17273}
17274
17275inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
17276 if (!ctx) { return false; }
17277 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17278
17279 impl::get_verify_callback() = std::move(callback);
17280
17281 if (impl::get_verify_callback()) {
17282 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, impl::openssl_verify_callback);
17283 } else {
17284 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
17285 }
17286 return true;
17287}
17288
17289inline long get_verify_error(const_session_t session) {
17290 if (!session) { return -1; }
17291 auto ssl = static_cast<SSL *>(const_cast<void *>(session));
17292 return SSL_get_verify_result(ssl);
17293}
17294
17295inline std::string verify_error_string(long error_code) {
17296 if (error_code == X509_V_OK) { return ""; }
17297 const char *str = X509_verify_cert_error_string(static_cast<int>(error_code));
17298 return str ? str : "unknown error";
17299}
17300
17301namespace impl {
17302
17303// OpenSSL-specific helpers for public API wrappers
17304inline ctx_t create_server_context_from_x509(X509 *cert, EVP_PKEY *key,
17305 X509_STORE *client_ca_store,
17306 int &out_error) {
17307 out_error = 0;
17308 auto cert_pem = x509_to_pem(cert);
17309 auto key_pem = evp_pkey_to_pem(key);
17310 if (cert_pem.empty() || key_pem.empty()) {
17311 out_error = static_cast<int>(ERR_get_error());
17312 return nullptr;
17313 }
17314
17315 auto ctx = create_server_context();
17316 if (!ctx) {
17317 out_error = static_cast<int>(get_error());
17318 return nullptr;
17319 }
17320
17321 if (!set_server_cert_pem(ctx, cert_pem.c_str(), key_pem.c_str(), nullptr)) {
17322 out_error = static_cast<int>(get_error());
17323 free_context(ctx);
17324 return nullptr;
17325 }
17326
17327 if (client_ca_store) {
17328 // Set cert store for verification (SSL_CTX_set_cert_store takes ownership)
17329 SSL_CTX_set_cert_store(static_cast<SSL_CTX *>(ctx), client_ca_store);
17330
17331 // Extract and set client CA list directly from store (more efficient than
17332 // PEM conversion)
17333 auto ca_list = extract_client_ca_list_from_store(client_ca_store);
17334 if (ca_list) {
17335 SSL_CTX_set_client_CA_list(static_cast<SSL_CTX *>(ctx), ca_list);
17336 }
17337
17338 set_verify_client(ctx, true);
17339 }
17340
17341 return ctx;
17342}
17343
17344inline void update_server_certs_from_x509(ctx_t ctx, X509 *cert, EVP_PKEY *key,
17345 X509_STORE *client_ca_store) {
17346 auto cert_pem = x509_to_pem(cert);
17347 auto key_pem = evp_pkey_to_pem(key);
17348
17349 if (!cert_pem.empty() && !key_pem.empty()) {
17350 update_server_cert(ctx, cert_pem.c_str(), key_pem.c_str(), nullptr);
17351 }
17352
17353 if (client_ca_store) {
17354 auto ca_pem = x509_store_to_pem(client_ca_store);
17355 if (!ca_pem.empty()) { update_server_client_ca(ctx, ca_pem.c_str()); }
17356 X509_STORE_free(client_ca_store);
17357 }
17358}
17359
17360inline ctx_t create_client_context_from_x509(X509 *cert, EVP_PKEY *key,
17361 const char *password,
17362 uint64_t &out_error) {
17363 out_error = 0;
17364 auto ctx = create_client_context();
17365 if (!ctx) {
17366 out_error = get_error();
17367 return nullptr;
17368 }
17369
17370 if (cert && key) {
17371 auto cert_pem = x509_to_pem(cert);
17372 auto key_pem = evp_pkey_to_pem(key);
17373 if (cert_pem.empty() || key_pem.empty()) {
17374 out_error = ERR_get_error();
17375 free_context(ctx);
17376 return nullptr;
17377 }
17378 if (!set_client_cert_pem(ctx, cert_pem.c_str(), key_pem.c_str(),
17379 password)) {
17380 out_error = get_error();
17381 free_context(ctx);
17382 return nullptr;
17383 }
17384 }
17385
17386 return ctx;
17387}
17388
17389} // namespace impl
17390
17391} // namespace tls
17392
17393// ClientImpl::set_ca_cert_store - defined here to use
17394// tls::impl::x509_store_to_pem Deprecated: converts X509_STORE to PEM and
17395// stores for redirect transfer
17396inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) {
17397 if (ca_cert_store) {
17398 ca_cert_pem_ = tls::impl::x509_store_to_pem(ca_cert_store);
17399 }
17400}
17401
17402inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
17403 X509_STORE *client_ca_cert_store) {
17404 ctx_ = tls::impl::create_server_context_from_x509(
17405 cert, private_key, client_ca_cert_store, last_ssl_error_);
17406}
17407
17408inline SSLServer::SSLServer(
17409 const std::function<bool(SSL_CTX &ssl_ctx)> &setup_ssl_ctx_callback) {
17410 // Use abstract API to create context
17411 ctx_ = tls::create_server_context();
17412 if (ctx_) {
17413 // Pass to OpenSSL-specific callback (ctx_ is SSL_CTX* internally)
17414 auto ssl_ctx = static_cast<SSL_CTX *>(ctx_);
17415 if (!setup_ssl_ctx_callback(*ssl_ctx)) {
17416 tls::free_context(ctx_);
17417 ctx_ = nullptr;
17418 }
17419 }
17420}
17421
17422inline SSL_CTX *SSLServer::ssl_context() const {
17423 return static_cast<SSL_CTX *>(ctx_);
17424}
17425
17426inline void SSLServer::update_certs(X509 *cert, EVP_PKEY *private_key,
17427 X509_STORE *client_ca_cert_store) {
17428 std::lock_guard<std::mutex> guard(ctx_mutex_);
17429 tls::impl::update_server_certs_from_x509(ctx_, cert, private_key,
17430 client_ca_cert_store);
17431}
17432
17433inline SSLClient::SSLClient(const std::string &host, int port,
17434 X509 *client_cert, EVP_PKEY *client_key,
17435 const std::string &private_key_password)
17436 : ClientImpl(host, port) {
17437 const char *password =
17438 private_key_password.empty() ? nullptr : private_key_password.c_str();
17439 ctx_ = tls::impl::create_client_context_from_x509(
17440 client_cert, client_key, password, last_backend_error_);
17441}
17442
17443inline long SSLClient::get_verify_result() const { return verify_result_; }
17444
17445inline void SSLClient::set_server_certificate_verifier(
17446 std::function<SSLVerifierResponse(SSL *ssl)> verifier) {
17447 // Wrap SSL* callback into backend-independent session_verifier_
17448 auto v = std::make_shared<std::function<SSLVerifierResponse(SSL *)>>(
17449 std::move(verifier));
17450 session_verifier_ = [v](tls::session_t session) {
17451 return (*v)(static_cast<SSL *>(session));
17452 };
17453}
17454
17455inline SSL_CTX *SSLClient::ssl_context() const {
17456 return static_cast<SSL_CTX *>(ctx_);
17457}
17458
17459inline bool SSLClient::verify_host(X509 *server_cert) const {
17460 /* Quote from RFC2818 section 3.1 "Server Identity"
17461
17462 If a subjectAltName extension of type dNSName is present, that MUST
17463 be used as the identity. Otherwise, the (most specific) Common Name
17464 field in the Subject field of the certificate MUST be used. Although
17465 the use of the Common Name is existing practice, it is deprecated and
17466 Certification Authorities are encouraged to use the dNSName instead.
17467
17468 Matching is performed using the matching rules specified by
17469 [RFC2459]. If more than one identity of a given type is present in
17470 the certificate (e.g., more than one dNSName name, a match in any one
17471 of the set is considered acceptable.) Names may contain the wildcard
17472 character * which is considered to match any single domain name
17473 component or component fragment. E.g., *.a.com matches foo.a.com but
17474 not bar.foo.a.com. f*.com matches foo.com but not bar.com.
17475
17476 In some cases, the URI is specified as an IP address rather than a
17477 hostname. In this case, the iPAddress subjectAltName must be present
17478 in the certificate and must exactly match the IP in the URI.
17479
17480 */
17481 return verify_host_with_subject_alt_name(server_cert) ||
17482 verify_host_with_common_name(server_cert);
17483}
17484
17485inline bool
17486SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
17487 auto ret = false;
17488
17489 auto type = GEN_DNS;
17490
17491 struct in6_addr addr6 = {};
17492 struct in_addr addr = {};
17493 size_t addr_len = 0;
17494
17495#ifndef __MINGW32__
17496 if (inet_pton(AF_INET6, host_.c_str(), &addr6)) {
17497 type = GEN_IPADD;
17498 addr_len = sizeof(struct in6_addr);
17499 } else if (inet_pton(AF_INET, host_.c_str(), &addr)) {
17500 type = GEN_IPADD;
17501 addr_len = sizeof(struct in_addr);
17502 }
17503#endif
17504
17505 auto alt_names = static_cast<const struct stack_st_GENERAL_NAME *>(
17506 X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
17507
17508 if (alt_names) {
17509 auto dsn_matched = false;
17510 auto ip_matched = false;
17511
17512 auto count = sk_GENERAL_NAME_num(alt_names);
17513
17514 for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
17515 auto val = sk_GENERAL_NAME_value(alt_names, i);
17516 if (!val || val->type != type) { continue; }
17517
17518 auto name =
17519 reinterpret_cast<const char *>(ASN1_STRING_get0_data(val->d.ia5));
17520 if (name == nullptr) { continue; }
17521
17522 auto name_len = static_cast<size_t>(ASN1_STRING_length(val->d.ia5));
17523
17524 switch (type) {
17525 case GEN_DNS:
17526 dsn_matched =
17527 detail::match_hostname(std::string(name, name_len), host_);
17528 break;
17529
17530 case GEN_IPADD:
17531 if (!memcmp(&addr6, name, addr_len) || !memcmp(&addr, name, addr_len)) {
17532 ip_matched = true;
17533 }
17534 break;
17535 }
17536 }
17537
17538 if (dsn_matched || ip_matched) { ret = true; }
17539 }
17540
17541 GENERAL_NAMES_free(const_cast<STACK_OF(GENERAL_NAME) *>(
17542 reinterpret_cast<const STACK_OF(GENERAL_NAME) *>(alt_names)));
17543 return ret;
17544}
17545
17546inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
17547 const auto subject_name = X509_get_subject_name(server_cert);
17548
17549 if (subject_name != nullptr) {
17550 char name[BUFSIZ];
17551 auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
17552 name, sizeof(name));
17553
17554 if (name_len != -1) {
17555 return detail::match_hostname(
17556 std::string(name, static_cast<size_t>(name_len)), host_);
17557 }
17558 }
17559
17560 return false;
17561}
17562
17563#endif // CPPHTTPLIB_OPENSSL_SUPPORT
17564
17565/*
17566 * Group 9: TLS abstraction layer - Mbed TLS backend
17567 */
17568
17569/*
17570 * Mbed TLS Backend Implementation
17571 */
17572
17573#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
17574namespace tls {
17575
17576namespace impl {
17577
17578// Mbed TLS session wrapper
17579struct MbedTlsSession {
17580 mbedtls_ssl_context ssl;
17581 socket_t sock = INVALID_SOCKET;
17582 std::string hostname; // For client: set via set_sni
17583 std::string sni_hostname; // For server: received from client via SNI callback
17584
17585 MbedTlsSession() { mbedtls_ssl_init(&ssl); }
17586
17587 ~MbedTlsSession() { mbedtls_ssl_free(&ssl); }
17588
17589 MbedTlsSession(const MbedTlsSession &) = delete;
17590 MbedTlsSession &operator=(const MbedTlsSession &) = delete;
17591};
17592
17593// Thread-local error code accessor for Mbed TLS (since it doesn't have an error
17594// queue)
17595inline int &mbedtls_last_error() {
17596 static thread_local int err = 0;
17597 return err;
17598}
17599
17600// Helper to map Mbed TLS error to ErrorCode
17601inline ErrorCode map_mbedtls_error(int ret, int &out_errno) {
17602 if (ret == 0) { return ErrorCode::Success; }
17603 if (ret == MBEDTLS_ERR_SSL_WANT_READ) { return ErrorCode::WantRead; }
17604 if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) { return ErrorCode::WantWrite; }
17605 if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
17606 return ErrorCode::PeerClosed;
17607 }
17608 if (ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_NET_SEND_FAILED ||
17609 ret == MBEDTLS_ERR_NET_RECV_FAILED) {
17610 out_errno = errno;
17611 return ErrorCode::SyscallError;
17612 }
17613 if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) {
17614 return ErrorCode::CertVerifyFailed;
17615 }
17616 return ErrorCode::Fatal;
17617}
17618
17619// BIO-like send callback for Mbed TLS
17620inline int mbedtls_net_send_cb(void *ctx, const unsigned char *buf,
17621 size_t len) {
17622 auto sock = *static_cast<socket_t *>(ctx);
17623#ifdef _WIN32
17624 auto ret =
17625 send(sock, reinterpret_cast<const char *>(buf), static_cast<int>(len), 0);
17626 if (ret == SOCKET_ERROR) {
17627 int err = WSAGetLastError();
17628 if (err == WSAEWOULDBLOCK) { return MBEDTLS_ERR_SSL_WANT_WRITE; }
17629 return MBEDTLS_ERR_NET_SEND_FAILED;
17630 }
17631#else
17632 auto ret = send(sock, buf, len, 0);
17633 if (ret < 0) {
17634 if (errno == EAGAIN || errno == EWOULDBLOCK) {
17635 return MBEDTLS_ERR_SSL_WANT_WRITE;
17636 }
17637 return MBEDTLS_ERR_NET_SEND_FAILED;
17638 }
17639#endif
17640 return static_cast<int>(ret);
17641}
17642
17643// BIO-like recv callback for Mbed TLS
17644inline int mbedtls_net_recv_cb(void *ctx, unsigned char *buf, size_t len) {
17645 auto sock = *static_cast<socket_t *>(ctx);
17646#ifdef _WIN32
17647 auto ret =
17648 recv(sock, reinterpret_cast<char *>(buf), static_cast<int>(len), 0);
17649 if (ret == SOCKET_ERROR) {
17650 int err = WSAGetLastError();
17651 if (err == WSAEWOULDBLOCK) { return MBEDTLS_ERR_SSL_WANT_READ; }
17652 return MBEDTLS_ERR_NET_RECV_FAILED;
17653 }
17654#else
17655 auto ret = recv(sock, buf, len, 0);
17656 if (ret < 0) {
17657 if (errno == EAGAIN || errno == EWOULDBLOCK) {
17658 return MBEDTLS_ERR_SSL_WANT_READ;
17659 }
17660 return MBEDTLS_ERR_NET_RECV_FAILED;
17661 }
17662#endif
17663 if (ret == 0) { return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY; }
17664 return static_cast<int>(ret);
17665}
17666
17667// MbedTlsContext constructor/destructor implementations
17668inline MbedTlsContext::MbedTlsContext() {
17669 mbedtls_ssl_config_init(&conf);
17670 mbedtls_entropy_init(&entropy);
17671 mbedtls_ctr_drbg_init(&ctr_drbg);
17672 mbedtls_x509_crt_init(&ca_chain);
17673 mbedtls_x509_crt_init(&own_cert);
17674 mbedtls_pk_init(&own_key);
17675}
17676
17677inline MbedTlsContext::~MbedTlsContext() {
17678 mbedtls_pk_free(&own_key);
17679 mbedtls_x509_crt_free(&own_cert);
17680 mbedtls_x509_crt_free(&ca_chain);
17681 mbedtls_ctr_drbg_free(&ctr_drbg);
17682 mbedtls_entropy_free(&entropy);
17683 mbedtls_ssl_config_free(&conf);
17684}
17685
17686// Thread-local storage for SNI captured during handshake
17687// This is needed because the SNI callback doesn't have a way to pass
17688// session-specific data before the session is fully set up
17689inline std::string &mbedpending_sni() {
17690 static thread_local std::string sni;
17691 return sni;
17692}
17693
17694// SNI callback for Mbed TLS server to capture client's SNI hostname
17695inline int mbedtls_sni_callback(void *p_ctx, mbedtls_ssl_context *ssl,
17696 const unsigned char *name, size_t name_len) {
17697 (void)p_ctx;
17698 (void)ssl;
17699
17700 // Store SNI name in thread-local storage
17701 // It will be retrieved and stored in the session after handshake
17702 if (name && name_len > 0) {
17703 mbedpending_sni().assign(reinterpret_cast<const char *>(name), name_len);
17704 } else {
17705 mbedpending_sni().clear();
17706 }
17707 return 0; // Accept any SNI
17708}
17709
17710inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
17711 int cert_depth, uint32_t *flags);
17712
17713// MbedTLS verify callback wrapper
17714inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
17715 int cert_depth, uint32_t *flags) {
17716 auto &callback = get_verify_callback();
17717 if (!callback) { return 0; } // Continue with default verification
17718
17719 // data points to the MbedTlsSession
17720 auto *session = static_cast<MbedTlsSession *>(data);
17721
17722 // Build context
17723 VerifyContext verify_ctx;
17724 verify_ctx.session = static_cast<session_t>(session);
17725 verify_ctx.cert = static_cast<cert_t>(crt);
17726 verify_ctx.depth = cert_depth;
17727 verify_ctx.preverify_ok = (*flags == 0);
17728 verify_ctx.error_code = static_cast<long>(*flags);
17729
17730 // Convert Mbed TLS flags to error string
17731 static thread_local char error_buf[256];
17732 if (*flags != 0) {
17733 mbedtls_x509_crt_verify_info(error_buf, sizeof(error_buf), "", *flags);
17734 verify_ctx.error_string = error_buf;
17735 } else {
17736 verify_ctx.error_string = nullptr;
17737 }
17738
17739 bool accepted = callback(verify_ctx);
17740
17741 if (accepted) {
17742 *flags = 0; // Clear all error flags
17743 return 0;
17744 }
17745 return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
17746}
17747
17748} // namespace impl
17749
17750inline ctx_t create_client_context() {
17751 auto ctx = new (std::nothrow) impl::MbedTlsContext();
17752 if (!ctx) { return nullptr; }
17753
17754 ctx->is_server = false;
17755
17756 // Seed the random number generator
17757 const char *pers = "httplib_client";
17758 int ret = mbedtls_ctr_drbg_seed(
17759 &ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy,
17760 reinterpret_cast<const unsigned char *>(pers), strlen(pers));
17761 if (ret != 0) {
17762 impl::mbedtls_last_error() = ret;
17763 delete ctx;
17764 return nullptr;
17765 }
17766
17767 // Set up SSL config for client
17768 ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_CLIENT,
17769 MBEDTLS_SSL_TRANSPORT_STREAM,
17770 MBEDTLS_SSL_PRESET_DEFAULT);
17771 if (ret != 0) {
17772 impl::mbedtls_last_error() = ret;
17773 delete ctx;
17774 return nullptr;
17775 }
17776
17777 // Set random number generator
17778 mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
17779
17780 // Default: verify peer certificate
17781 mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
17782
17783 // Set minimum TLS version to 1.2
17784#ifdef CPPHTTPLIB_MBEDTLS_V3
17785 mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2);
17786#else
17787 mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,
17788 MBEDTLS_SSL_MINOR_VERSION_3);
17789#endif
17790
17791 return static_cast<ctx_t>(ctx);
17792}
17793
17794inline ctx_t create_server_context() {
17795 auto ctx = new (std::nothrow) impl::MbedTlsContext();
17796 if (!ctx) { return nullptr; }
17797
17798 ctx->is_server = true;
17799
17800 // Seed the random number generator
17801 const char *pers = "httplib_server";
17802 int ret = mbedtls_ctr_drbg_seed(
17803 &ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy,
17804 reinterpret_cast<const unsigned char *>(pers), strlen(pers));
17805 if (ret != 0) {
17806 impl::mbedtls_last_error() = ret;
17807 delete ctx;
17808 return nullptr;
17809 }
17810
17811 // Set up SSL config for server
17812 ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_SERVER,
17813 MBEDTLS_SSL_TRANSPORT_STREAM,
17814 MBEDTLS_SSL_PRESET_DEFAULT);
17815 if (ret != 0) {
17816 impl::mbedtls_last_error() = ret;
17817 delete ctx;
17818 return nullptr;
17819 }
17820
17821 // Set random number generator
17822 mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
17823
17824 // Default: don't verify client
17825 mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE);
17826
17827 // Set minimum TLS version to 1.2
17828#ifdef CPPHTTPLIB_MBEDTLS_V3
17829 mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2);
17830#else
17831 mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,
17832 MBEDTLS_SSL_MINOR_VERSION_3);
17833#endif
17834
17835 // Set SNI callback to capture client's SNI hostname
17836 mbedtls_ssl_conf_sni(&ctx->conf, impl::mbedtls_sni_callback, nullptr);
17837
17838 return static_cast<ctx_t>(ctx);
17839}
17840
17841inline void free_context(ctx_t ctx) {
17842 if (ctx) { delete static_cast<impl::MbedTlsContext *>(ctx); }
17843}
17844
17845inline bool set_min_version(ctx_t ctx, Version version) {
17846 if (!ctx) { return false; }
17847 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
17848
17849#ifdef CPPHTTPLIB_MBEDTLS_V3
17850 // Mbed TLS 3.x uses mbedtls_ssl_protocol_version enum
17851 mbedtls_ssl_protocol_version min_ver = MBEDTLS_SSL_VERSION_TLS1_2;
17852 if (version >= Version::TLS1_3) {
17853#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
17854 min_ver = MBEDTLS_SSL_VERSION_TLS1_3;
17855#endif
17856 }
17857 mbedtls_ssl_conf_min_tls_version(&mctx->conf, min_ver);
17858#else
17859 // Mbed TLS 2.x uses major/minor version numbers
17860 int major = MBEDTLS_SSL_MAJOR_VERSION_3;
17861 int minor = MBEDTLS_SSL_MINOR_VERSION_3; // TLS 1.2
17862 if (version >= Version::TLS1_3) {
17863#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
17864 minor = MBEDTLS_SSL_MINOR_VERSION_4; // TLS 1.3
17865#else
17866 minor = MBEDTLS_SSL_MINOR_VERSION_3; // Fall back to TLS 1.2
17867#endif
17868 }
17869 mbedtls_ssl_conf_min_version(&mctx->conf, major, minor);
17870#endif
17871 return true;
17872}
17873
17874inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
17875 if (!ctx || !pem) { return false; }
17876 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
17877
17878 // mbedtls_x509_crt_parse expects null-terminated string for PEM
17879 // Add null terminator if not present
17880 std::string pem_str(pem, len);
17881 int ret = mbedtls_x509_crt_parse(
17882 &mctx->ca_chain, reinterpret_cast<const unsigned char *>(pem_str.c_str()),
17883 pem_str.size() + 1);
17884 if (ret != 0) {
17885 impl::mbedtls_last_error() = ret;
17886 return false;
17887 }
17888
17889 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
17890 return true;
17891}
17892
17893inline bool load_ca_file(ctx_t ctx, const char *file_path) {
17894 if (!ctx || !file_path) { return false; }
17895 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
17896
17897 int ret = mbedtls_x509_crt_parse_file(&mctx->ca_chain, file_path);
17898 if (ret != 0) {
17899 impl::mbedtls_last_error() = ret;
17900 return false;
17901 }
17902
17903 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
17904 return true;
17905}
17906
17907inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
17908 if (!ctx || !dir_path) { return false; }
17909 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
17910
17911 int ret = mbedtls_x509_crt_parse_path(&mctx->ca_chain, dir_path);
17912 if (ret < 0) { // Returns number of certs on success, negative on error
17913 impl::mbedtls_last_error() = ret;
17914 return false;
17915 }
17916
17917 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
17918 return true;
17919}
17920
17921inline bool load_system_certs(ctx_t ctx) {
17922 if (!ctx) { return false; }
17923 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
17924 bool loaded = false;
17925
17926#ifdef _WIN32
17927 loaded = impl::enumerate_windows_system_certs(
17928 [&](const unsigned char *data, size_t len) {
17929 return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0;
17930 });
17931#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
17932 loaded = impl::enumerate_macos_keychain_certs(
17933 [&](const unsigned char *data, size_t len) {
17934 return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0;
17935 });
17936#else
17937 for (auto path = impl::system_ca_paths(); *path; ++path) {
17938 if (mbedtls_x509_crt_parse_file(&mctx->ca_chain, *path) >= 0) {
17939 loaded = true;
17940 break;
17941 }
17942 }
17943
17944 if (!loaded) {
17945 for (auto dir = impl::system_ca_dirs(); *dir; ++dir) {
17946 if (mbedtls_x509_crt_parse_path(&mctx->ca_chain, *dir) >= 0) {
17947 loaded = true;
17948 break;
17949 }
17950 }
17951 }
17952#endif
17953
17954 if (loaded) {
17955 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
17956 }
17957 return loaded;
17958}
17959
17960inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
17961 const char *password) {
17962 if (!ctx || !cert || !key) { return false; }
17963 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
17964
17965 // Parse certificate
17966 std::string cert_str(cert);
17967 int ret = mbedtls_x509_crt_parse(
17968 &mctx->own_cert,
17969 reinterpret_cast<const unsigned char *>(cert_str.c_str()),
17970 cert_str.size() + 1);
17971 if (ret != 0) {
17972 impl::mbedtls_last_error() = ret;
17973 return false;
17974 }
17975
17976 // Parse private key
17977 std::string key_str(key);
17978 const unsigned char *pwd =
17979 password ? reinterpret_cast<const unsigned char *>(password) : nullptr;
17980 size_t pwd_len = password ? strlen(password) : 0;
17981
17982#ifdef CPPHTTPLIB_MBEDTLS_V3
17983 ret = mbedtls_pk_parse_key(
17984 &mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
17985 key_str.size() + 1, pwd, pwd_len, mbedtls_ctr_drbg_random,
17986 &mctx->ctr_drbg);
17987#else
17988 ret = mbedtls_pk_parse_key(
17989 &mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
17990 key_str.size() + 1, pwd, pwd_len);
17991#endif
17992 if (ret != 0) {
17993 impl::mbedtls_last_error() = ret;
17994 return false;
17995 }
17996
17997 // Verify that the certificate and private key match
17998#ifdef CPPHTTPLIB_MBEDTLS_V3
17999 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
18000 mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18001#else
18002 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key);
18003#endif
18004 if (ret != 0) {
18005 impl::mbedtls_last_error() = ret;
18006 return false;
18007 }
18008
18009 ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
18010 if (ret != 0) {
18011 impl::mbedtls_last_error() = ret;
18012 return false;
18013 }
18014
18015 return true;
18016}
18017
18018inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
18019 const char *key_path, const char *password) {
18020 if (!ctx || !cert_path || !key_path) { return false; }
18021 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18022
18023 // Parse certificate file
18024 int ret = mbedtls_x509_crt_parse_file(&mctx->own_cert, cert_path);
18025 if (ret != 0) {
18026 impl::mbedtls_last_error() = ret;
18027 return false;
18028 }
18029
18030 // Parse private key file
18031#ifdef CPPHTTPLIB_MBEDTLS_V3
18032 ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password,
18033 mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18034#else
18035 ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password);
18036#endif
18037 if (ret != 0) {
18038 impl::mbedtls_last_error() = ret;
18039 return false;
18040 }
18041
18042 // Verify that the certificate and private key match
18043#ifdef CPPHTTPLIB_MBEDTLS_V3
18044 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
18045 mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18046#else
18047 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key);
18048#endif
18049 if (ret != 0) {
18050 impl::mbedtls_last_error() = ret;
18051 return false;
18052 }
18053
18054 ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
18055 if (ret != 0) {
18056 impl::mbedtls_last_error() = ret;
18057 return false;
18058 }
18059
18060 return true;
18061}
18062
18063inline void set_verify_client(ctx_t ctx, bool require) {
18064 if (!ctx) { return; }
18065 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18066 mctx->verify_client = require;
18067 if (require) {
18068 mbedtls_ssl_conf_authmode(&mctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
18069 } else {
18070 // If a verify callback is set, use OPTIONAL mode to ensure the callback
18071 // is called (matching OpenSSL behavior). Otherwise use NONE.
18072 mbedtls_ssl_conf_authmode(&mctx->conf, mctx->has_verify_callback
18073 ? MBEDTLS_SSL_VERIFY_OPTIONAL
18074 : MBEDTLS_SSL_VERIFY_NONE);
18075 }
18076}
18077
18078inline session_t create_session(ctx_t ctx, socket_t sock) {
18079 if (!ctx || sock == INVALID_SOCKET) { return nullptr; }
18080 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18081
18082 auto session = new (std::nothrow) impl::MbedTlsSession();
18083 if (!session) { return nullptr; }
18084
18085 session->sock = sock;
18086
18087 int ret = mbedtls_ssl_setup(&session->ssl, &mctx->conf);
18088 if (ret != 0) {
18089 impl::mbedtls_last_error() = ret;
18090 delete session;
18091 return nullptr;
18092 }
18093
18094 // Set BIO callbacks
18095 mbedtls_ssl_set_bio(&session->ssl, &session->sock, impl::mbedtls_net_send_cb,
18096 impl::mbedtls_net_recv_cb, nullptr);
18097
18098 // Set per-session verify callback with session pointer if callback is
18099 // registered
18100 if (mctx->has_verify_callback) {
18101 mbedtls_ssl_set_verify(&session->ssl, impl::mbedtls_verify_callback,
18102 session);
18103 }
18104
18105 return static_cast<session_t>(session);
18106}
18107
18108inline void free_session(session_t session) {
18109 if (session) { delete static_cast<impl::MbedTlsSession *>(session); }
18110}
18111
18112inline bool set_sni(session_t session, const char *hostname) {
18113 if (!session || !hostname) { return false; }
18114 auto msession = static_cast<impl::MbedTlsSession *>(session);
18115
18116 int ret = mbedtls_ssl_set_hostname(&msession->ssl, hostname);
18117 if (ret != 0) {
18118 impl::mbedtls_last_error() = ret;
18119 return false;
18120 }
18121
18122 msession->hostname = hostname;
18123 return true;
18124}
18125
18126inline bool set_hostname(session_t session, const char *hostname) {
18127 // In Mbed TLS, set_hostname also sets up hostname verification
18128 return set_sni(session, hostname);
18129}
18130
18131inline TlsError connect(session_t session) {
18132 TlsError err;
18133 if (!session) {
18134 err.code = ErrorCode::Fatal;
18135 return err;
18136 }
18137
18138 auto msession = static_cast<impl::MbedTlsSession *>(session);
18139 int ret = mbedtls_ssl_handshake(&msession->ssl);
18140
18141 if (ret == 0) {
18142 err.code = ErrorCode::Success;
18143 } else {
18144 err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18145 err.backend_code = static_cast<uint64_t>(-ret);
18146 impl::mbedtls_last_error() = ret;
18147 }
18148
18149 return err;
18150}
18151
18152inline TlsError accept(session_t session) {
18153 // Same as connect for Mbed TLS - handshake works for both client and server
18154 auto result = connect(session);
18155
18156 // After successful handshake, capture SNI from thread-local storage
18157 if (result.code == ErrorCode::Success && session) {
18158 auto msession = static_cast<impl::MbedTlsSession *>(session);
18159 msession->sni_hostname = std::move(impl::mbedpending_sni());
18160 impl::mbedpending_sni().clear();
18161 }
18162
18163 return result;
18164}
18165
18166inline bool connect_nonblocking(session_t session, socket_t sock,
18167 time_t timeout_sec, time_t timeout_usec,
18168 TlsError *err) {
18169 if (!session) {
18170 if (err) { err->code = ErrorCode::Fatal; }
18171 return false;
18172 }
18173
18174 auto msession = static_cast<impl::MbedTlsSession *>(session);
18175
18176 // Set socket to non-blocking mode
18177 detail::set_nonblocking(sock, true);
18178 auto cleanup =
18179 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
18180
18181 int ret;
18182 while ((ret = mbedtls_ssl_handshake(&msession->ssl)) != 0) {
18183 if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
18184 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
18185 continue;
18186 }
18187 } else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
18188 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
18189 continue;
18190 }
18191 }
18192
18193 // TlsError or timeout
18194 if (err) {
18195 err->code = impl::map_mbedtls_error(ret, err->sys_errno);
18196 err->backend_code = static_cast<uint64_t>(-ret);
18197 }
18198 impl::mbedtls_last_error() = ret;
18199 return false;
18200 }
18201
18202 if (err) { err->code = ErrorCode::Success; }
18203 return true;
18204}
18205
18206inline bool accept_nonblocking(session_t session, socket_t sock,
18207 time_t timeout_sec, time_t timeout_usec,
18208 TlsError *err) {
18209 // Same implementation as connect for Mbed TLS
18210 bool result =
18211 connect_nonblocking(session, sock, timeout_sec, timeout_usec, err);
18212
18213 // After successful handshake, capture SNI from thread-local storage
18214 if (result && session) {
18215 auto msession = static_cast<impl::MbedTlsSession *>(session);
18216 msession->sni_hostname = std::move(impl::mbedpending_sni());
18217 impl::mbedpending_sni().clear();
18218 }
18219
18220 return result;
18221}
18222
18223inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
18224 if (!session || !buf) {
18225 err.code = ErrorCode::Fatal;
18226 return -1;
18227 }
18228
18229 auto msession = static_cast<impl::MbedTlsSession *>(session);
18230 int ret =
18231 mbedtls_ssl_read(&msession->ssl, static_cast<unsigned char *>(buf), len);
18232
18233 if (ret > 0) {
18234 err.code = ErrorCode::Success;
18235 return static_cast<ssize_t>(ret);
18236 }
18237
18238 if (ret == 0) {
18239 err.code = ErrorCode::PeerClosed;
18240 return 0;
18241 }
18242
18243 err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18244 err.backend_code = static_cast<uint64_t>(-ret);
18245 impl::mbedtls_last_error() = ret;
18246 return -1;
18247}
18248
18249inline ssize_t write(session_t session, const void *buf, size_t len,
18250 TlsError &err) {
18251 if (!session || !buf) {
18252 err.code = ErrorCode::Fatal;
18253 return -1;
18254 }
18255
18256 auto msession = static_cast<impl::MbedTlsSession *>(session);
18257 int ret = mbedtls_ssl_write(&msession->ssl,
18258 static_cast<const unsigned char *>(buf), len);
18259
18260 if (ret > 0) {
18261 err.code = ErrorCode::Success;
18262 return static_cast<ssize_t>(ret);
18263 }
18264
18265 if (ret == 0) {
18266 err.code = ErrorCode::PeerClosed;
18267 return 0;
18268 }
18269
18270 err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18271 err.backend_code = static_cast<uint64_t>(-ret);
18272 impl::mbedtls_last_error() = ret;
18273 return -1;
18274}
18275
18276inline int pending(const_session_t session) {
18277 if (!session) { return 0; }
18278 auto msession =
18279 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18280 return static_cast<int>(mbedtls_ssl_get_bytes_avail(&msession->ssl));
18281}
18282
18283inline void shutdown(session_t session, bool graceful) {
18284 if (!session) { return; }
18285 auto msession = static_cast<impl::MbedTlsSession *>(session);
18286
18287 if (graceful) {
18288 // Try to send close_notify, but don't block forever
18289 int ret;
18290 int attempts = 0;
18291 while ((ret = mbedtls_ssl_close_notify(&msession->ssl)) != 0 &&
18292 attempts < 3) {
18293 if (ret != MBEDTLS_ERR_SSL_WANT_READ &&
18294 ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
18295 break;
18296 }
18297 attempts++;
18298 }
18299 }
18300}
18301
18302inline bool is_peer_closed(session_t session, socket_t sock) {
18303 if (!session || sock == INVALID_SOCKET) { return true; }
18304 auto msession = static_cast<impl::MbedTlsSession *>(session);
18305
18306 // Check if there's already decrypted data available in the TLS buffer
18307 // If so, the connection is definitely alive
18308 if (mbedtls_ssl_get_bytes_avail(&msession->ssl) > 0) { return false; }
18309
18310 // Set socket to non-blocking to avoid blocking on read
18311 detail::set_nonblocking(sock, true);
18312 auto cleanup =
18313 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
18314
18315 // Try a 1-byte read to check connection status
18316 // Note: This will consume the byte if data is available, but for the
18317 // purpose of checking if peer is closed, this should be acceptable
18318 // since we're only called when we expect the connection might be closing
18319 unsigned char buf;
18320 int ret = mbedtls_ssl_read(&msession->ssl, &buf, 1);
18321
18322 // If we got data or WANT_READ (would block), connection is alive
18323 if (ret > 0 || ret == MBEDTLS_ERR_SSL_WANT_READ) { return false; }
18324
18325 // If we get a peer close notify or a connection reset, the peer is closed
18326 return ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY ||
18327 ret == MBEDTLS_ERR_NET_CONN_RESET || ret == 0;
18328}
18329
18330inline cert_t get_peer_cert(const_session_t session) {
18331 if (!session) { return nullptr; }
18332 auto msession =
18333 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18334
18335 // Mbed TLS returns a pointer to the internal peer cert chain.
18336 // WARNING: This pointer is only valid while the session is active.
18337 // Do not use the certificate after calling free_session().
18338 const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&msession->ssl);
18339 return const_cast<mbedtls_x509_crt *>(cert);
18340}
18341
18342inline void free_cert(cert_t cert) {
18343 // Mbed TLS: peer certificate is owned by the SSL context.
18344 // No-op here, but callers should still call this for cross-backend
18345 // portability.
18346 (void)cert;
18347}
18348
18349inline bool verify_hostname(cert_t cert, const char *hostname) {
18350 if (!cert || !hostname) { return false; }
18351 auto mcert = static_cast<const mbedtls_x509_crt *>(cert);
18352 std::string host_str(hostname);
18353
18354 // Check if hostname is an IP address
18355 bool is_ip = impl::is_ipv4_address(host_str);
18356 unsigned char ip_bytes[4];
18357 if (is_ip) { impl::parse_ipv4(host_str, ip_bytes); }
18358
18359 // Check Subject Alternative Names (SAN)
18360 // In Mbed TLS 3.x, subject_alt_names contains raw values without ASN.1 tags
18361 // - DNS names: raw string bytes
18362 // - IP addresses: raw IP bytes (4 for IPv4, 16 for IPv6)
18363 const mbedtls_x509_sequence *san = &mcert->subject_alt_names;
18364 while (san != nullptr && san->buf.p != nullptr && san->buf.len > 0) {
18365 const unsigned char *p = san->buf.p;
18366 size_t len = san->buf.len;
18367
18368 if (is_ip) {
18369 // Check if this SAN is an IPv4 address (4 bytes)
18370 if (len == 4 && memcmp(p, ip_bytes, 4) == 0) { return true; }
18371 // Check if this SAN is an IPv6 address (16 bytes) - skip for now
18372 } else {
18373 // Check if this SAN is a DNS name (printable ASCII string)
18374 bool is_dns = len > 0;
18375 for (size_t i = 0; i < len && is_dns; i++) {
18376 if (p[i] < 32 || p[i] > 126) { is_dns = false; }
18377 }
18378 if (is_dns) {
18379 std::string san_name(reinterpret_cast<const char *>(p), len);
18380 if (detail::match_hostname(san_name, host_str)) { return true; }
18381 }
18382 }
18383 san = san->next;
18384 }
18385
18386 // Fallback: Check Common Name (CN) in subject
18387 char cn[256];
18388 int ret = mbedtls_x509_dn_gets(cn, sizeof(cn), &mcert->subject);
18389 if (ret > 0) {
18390 std::string cn_str(cn);
18391
18392 // Look for "CN=" in the DN string
18393 size_t cn_pos = cn_str.find("CN=");
18394 if (cn_pos != std::string::npos) {
18395 size_t start = cn_pos + 3;
18396 size_t end = cn_str.find(',', start);
18397 std::string cn_value =
18398 cn_str.substr(start, end == std::string::npos ? end : end - start);
18399
18400 if (detail::match_hostname(cn_value, host_str)) { return true; }
18401 }
18402 }
18403
18404 return false;
18405}
18406
18407inline uint64_t hostname_mismatch_code() {
18408 return static_cast<uint64_t>(MBEDTLS_X509_BADCERT_CN_MISMATCH);
18409}
18410
18411inline long get_verify_result(const_session_t session) {
18412 if (!session) { return -1; }
18413 auto msession =
18414 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18415 uint32_t flags = mbedtls_ssl_get_verify_result(&msession->ssl);
18416 // Return 0 (X509_V_OK equivalent) if verification passed
18417 return flags == 0 ? 0 : static_cast<long>(flags);
18418}
18419
18420inline std::string get_cert_subject_cn(cert_t cert) {
18421 if (!cert) return "";
18422 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18423
18424 // Find the CN in the subject
18425 const mbedtls_x509_name *name = &x509->subject;
18426 while (name != nullptr) {
18427 if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid) == 0) {
18428 return std::string(reinterpret_cast<const char *>(name->val.p),
18429 name->val.len);
18430 }
18431 name = name->next;
18432 }
18433 return "";
18434}
18435
18436inline std::string get_cert_issuer_name(cert_t cert) {
18437 if (!cert) return "";
18438 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18439
18440 // Build a human-readable issuer name string
18441 char buf[512];
18442 int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &x509->issuer);
18443 if (ret < 0) return "";
18444 return std::string(buf);
18445}
18446
18447inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
18448 sans.clear();
18449 if (!cert) return false;
18450 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18451
18452 // Parse the Subject Alternative Name extension
18453 const mbedtls_x509_sequence *cur = &x509->subject_alt_names;
18454 while (cur != nullptr) {
18455 if (cur->buf.len > 0) {
18456 // Mbed TLS stores SAN as ASN.1 sequences
18457 // The tag byte indicates the type
18458 const unsigned char *p = cur->buf.p;
18459 size_t len = cur->buf.len;
18460
18461 // First byte is the tag
18462 unsigned char tag = *p;
18463 p++;
18464 len--;
18465
18466 // Parse length (simple single-byte length assumed)
18467 if (len > 0 && *p < 0x80) {
18468 size_t value_len = *p;
18469 p++;
18470 len--;
18471
18472 if (value_len <= len) {
18473 SanEntry entry;
18474 // ASN.1 context tags for GeneralName
18475 switch (tag & 0x1F) {
18476 case 2: // dNSName
18477 entry.type = SanType::DNS;
18478 entry.value =
18479 std::string(reinterpret_cast<const char *>(p), value_len);
18480 break;
18481 case 7: // iPAddress
18482 entry.type = SanType::IP;
18483 if (value_len == 4) {
18484 // IPv4
18485 char buf[16];
18486 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
18487 entry.value = buf;
18488 } else if (value_len == 16) {
18489 // IPv6
18490 char buf[64];
18491 snprintf(buf, sizeof(buf),
18492 "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
18493 "%02x%02x:%02x%02x:%02x%02x:%02x%02x",
18494 p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8],
18495 p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
18496 entry.value = buf;
18497 }
18498 break;
18499 case 1: // rfc822Name (email)
18500 entry.type = SanType::EMAIL;
18501 entry.value =
18502 std::string(reinterpret_cast<const char *>(p), value_len);
18503 break;
18504 case 6: // uniformResourceIdentifier
18505 entry.type = SanType::URI;
18506 entry.value =
18507 std::string(reinterpret_cast<const char *>(p), value_len);
18508 break;
18509 default: entry.type = SanType::OTHER; break;
18510 }
18511
18512 if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
18513 }
18514 }
18515 }
18516 cur = cur->next;
18517 }
18518 return true;
18519}
18520
18521inline bool get_cert_validity(cert_t cert, time_t &not_before,
18522 time_t &not_after) {
18523 if (!cert) return false;
18524 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18525
18526 // Convert mbedtls_x509_time to time_t
18527 auto to_time_t = [](const mbedtls_x509_time &t) -> time_t {
18528 struct tm tm_time = {};
18529 tm_time.tm_year = t.year - 1900;
18530 tm_time.tm_mon = t.mon - 1;
18531 tm_time.tm_mday = t.day;
18532 tm_time.tm_hour = t.hour;
18533 tm_time.tm_min = t.min;
18534 tm_time.tm_sec = t.sec;
18535#ifdef _WIN32
18536 return _mkgmtime(&tm_time);
18537#else
18538 return timegm(&tm_time);
18539#endif
18540 };
18541
18542 not_before = to_time_t(x509->valid_from);
18543 not_after = to_time_t(x509->valid_to);
18544 return true;
18545}
18546
18547inline std::string get_cert_serial(cert_t cert) {
18548 if (!cert) return "";
18549 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18550
18551 // Convert serial number to hex string
18552 std::string result;
18553 result.reserve(x509->serial.len * 2);
18554 for (size_t i = 0; i < x509->serial.len; i++) {
18555 char hex[3];
18556 snprintf(hex, sizeof(hex), "%02X", x509->serial.p[i]);
18557 result += hex;
18558 }
18559 return result;
18560}
18561
18562inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
18563 if (!cert) return false;
18564 auto crt = static_cast<mbedtls_x509_crt *>(cert);
18565 if (!crt->raw.p || crt->raw.len == 0) return false;
18566 der.assign(crt->raw.p, crt->raw.p + crt->raw.len);
18567 return true;
18568}
18569
18570inline const char *get_sni(const_session_t session) {
18571 if (!session) return nullptr;
18572 auto msession = static_cast<const impl::MbedTlsSession *>(session);
18573
18574 // For server: return SNI received from client during handshake
18575 if (!msession->sni_hostname.empty()) {
18576 return msession->sni_hostname.c_str();
18577 }
18578
18579 // For client: return the hostname set via set_sni
18580 if (!msession->hostname.empty()) { return msession->hostname.c_str(); }
18581
18582 return nullptr;
18583}
18584
18585inline uint64_t peek_error() {
18586 // Mbed TLS doesn't have an error queue, return the last error
18587 return static_cast<uint64_t>(-impl::mbedtls_last_error());
18588}
18589
18590inline uint64_t get_error() {
18591 // Mbed TLS doesn't have an error queue, return and clear the last error
18592 uint64_t err = static_cast<uint64_t>(-impl::mbedtls_last_error());
18593 impl::mbedtls_last_error() = 0;
18594 return err;
18595}
18596
18597inline std::string error_string(uint64_t code) {
18598 char buf[256];
18599 mbedtls_strerror(-static_cast<int>(code), buf, sizeof(buf));
18600 return std::string(buf);
18601}
18602
18603inline ca_store_t create_ca_store(const char *pem, size_t len) {
18604 auto *ca_chain = new (std::nothrow) mbedtls_x509_crt;
18605 if (!ca_chain) { return nullptr; }
18606
18607 mbedtls_x509_crt_init(ca_chain);
18608
18609 // mbedtls_x509_crt_parse expects null-terminated PEM
18610 int ret = mbedtls_x509_crt_parse(ca_chain,
18611 reinterpret_cast<const unsigned char *>(pem),
18612 len + 1); // +1 for null terminator
18613 if (ret != 0) {
18614 // Try without +1 in case PEM is already null-terminated
18615 ret = mbedtls_x509_crt_parse(
18616 ca_chain, reinterpret_cast<const unsigned char *>(pem), len);
18617 if (ret != 0) {
18618 mbedtls_x509_crt_free(ca_chain);
18619 delete ca_chain;
18620 return nullptr;
18621 }
18622 }
18623
18624 return static_cast<ca_store_t>(ca_chain);
18625}
18626
18627inline void free_ca_store(ca_store_t store) {
18628 if (store) {
18629 auto *ca_chain = static_cast<mbedtls_x509_crt *>(store);
18630 mbedtls_x509_crt_free(ca_chain);
18631 delete ca_chain;
18632 }
18633}
18634
18635inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
18636 if (!ctx || !store) { return false; }
18637 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18638 auto *ca_chain = static_cast<mbedtls_x509_crt *>(store);
18639
18640 // Free existing CA chain
18641 mbedtls_x509_crt_free(&mbed_ctx->ca_chain);
18642 mbedtls_x509_crt_init(&mbed_ctx->ca_chain);
18643
18644 // Copy the CA chain (deep copy)
18645 // Parse from the raw data of the source cert
18646 mbedtls_x509_crt *src = ca_chain;
18647 while (src != nullptr) {
18648 int ret = mbedtls_x509_crt_parse_der(&mbed_ctx->ca_chain, src->raw.p,
18649 src->raw.len);
18650 if (ret != 0) { return false; }
18651 src = src->next;
18652 }
18653
18654 // Update the SSL config to use the new CA chain
18655 mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
18656 return true;
18657}
18658
18659inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
18660 certs.clear();
18661 if (!ctx) { return 0; }
18662 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18663
18664 // Iterate through the CA chain
18665 mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
18666 while (cert != nullptr && cert->raw.len > 0) {
18667 // Create a copy of the certificate for the caller
18668 auto *copy = new mbedtls_x509_crt;
18669 mbedtls_x509_crt_init(copy);
18670 int ret = mbedtls_x509_crt_parse_der(copy, cert->raw.p, cert->raw.len);
18671 if (ret == 0) {
18672 certs.push_back(static_cast<cert_t>(copy));
18673 } else {
18674 mbedtls_x509_crt_free(copy);
18675 delete copy;
18676 }
18677 cert = cert->next;
18678 }
18679 return certs.size();
18680}
18681
18682inline std::vector<std::string> get_ca_names(ctx_t ctx) {
18683 std::vector<std::string> names;
18684 if (!ctx) { return names; }
18685 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18686
18687 // Iterate through the CA chain
18688 mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
18689 while (cert != nullptr && cert->raw.len > 0) {
18690 char buf[512];
18691 int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &cert->subject);
18692 if (ret > 0) { names.push_back(buf); }
18693 cert = cert->next;
18694 }
18695 return names;
18696}
18697
18698inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
18699 const char *key_pem, const char *password) {
18700 if (!ctx || !cert_pem || !key_pem) { return false; }
18701 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18702
18703 // Free existing certificate and key
18704 mbedtls_x509_crt_free(&mbed_ctx->own_cert);
18705 mbedtls_pk_free(&mbed_ctx->own_key);
18706 mbedtls_x509_crt_init(&mbed_ctx->own_cert);
18707 mbedtls_pk_init(&mbed_ctx->own_key);
18708
18709 // Parse certificate PEM
18710 int ret = mbedtls_x509_crt_parse(
18711 &mbed_ctx->own_cert, reinterpret_cast<const unsigned char *>(cert_pem),
18712 strlen(cert_pem) + 1);
18713 if (ret != 0) {
18714 impl::mbedtls_last_error() = ret;
18715 return false;
18716 }
18717
18718 // Parse private key PEM
18719#ifdef CPPHTTPLIB_MBEDTLS_V3
18720 ret = mbedtls_pk_parse_key(
18721 &mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
18722 strlen(key_pem) + 1,
18723 password ? reinterpret_cast<const unsigned char *>(password) : nullptr,
18724 password ? strlen(password) : 0, mbedtls_ctr_drbg_random,
18725 &mbed_ctx->ctr_drbg);
18726#else
18727 ret = mbedtls_pk_parse_key(
18728 &mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
18729 strlen(key_pem) + 1,
18730 password ? reinterpret_cast<const unsigned char *>(password) : nullptr,
18731 password ? strlen(password) : 0);
18732#endif
18733 if (ret != 0) {
18734 impl::mbedtls_last_error() = ret;
18735 return false;
18736 }
18737
18738 // Configure SSL to use the new certificate and key
18739 ret = mbedtls_ssl_conf_own_cert(&mbed_ctx->conf, &mbed_ctx->own_cert,
18740 &mbed_ctx->own_key);
18741 if (ret != 0) {
18742 impl::mbedtls_last_error() = ret;
18743 return false;
18744 }
18745
18746 return true;
18747}
18748
18749inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
18750 if (!ctx || !ca_pem) { return false; }
18751 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18752
18753 // Free existing CA chain
18754 mbedtls_x509_crt_free(&mbed_ctx->ca_chain);
18755 mbedtls_x509_crt_init(&mbed_ctx->ca_chain);
18756
18757 // Parse CA PEM
18758 int ret = mbedtls_x509_crt_parse(
18759 &mbed_ctx->ca_chain, reinterpret_cast<const unsigned char *>(ca_pem),
18760 strlen(ca_pem) + 1);
18761 if (ret != 0) {
18762 impl::mbedtls_last_error() = ret;
18763 return false;
18764 }
18765
18766 // Update SSL config to use new CA chain
18767 mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
18768 return true;
18769}
18770
18771inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
18772 if (!ctx) { return false; }
18773 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18774
18775 impl::get_verify_callback() = std::move(callback);
18776 mbed_ctx->has_verify_callback =
18777 static_cast<bool>(impl::get_verify_callback());
18778
18779 if (mbed_ctx->has_verify_callback) {
18780 // Set OPTIONAL mode to ensure callback is called even when verification
18781 // is disabled (matching OpenSSL behavior where SSL_VERIFY_PEER is set)
18782 mbedtls_ssl_conf_authmode(&mbed_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
18783 mbedtls_ssl_conf_verify(&mbed_ctx->conf, impl::mbedtls_verify_callback,
18784 nullptr);
18785 } else {
18786 mbedtls_ssl_conf_verify(&mbed_ctx->conf, nullptr, nullptr);
18787 }
18788 return true;
18789}
18790
18791inline long get_verify_error(const_session_t session) {
18792 if (!session) { return -1; }
18793 auto *msession =
18794 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18795 return static_cast<long>(mbedtls_ssl_get_verify_result(&msession->ssl));
18796}
18797
18798inline std::string verify_error_string(long error_code) {
18799 if (error_code == 0) { return ""; }
18800 char buf[256];
18801 mbedtls_x509_crt_verify_info(buf, sizeof(buf), "",
18802 static_cast<uint32_t>(error_code));
18803 // Remove trailing newline if present
18804 std::string result(buf);
18805 while (!result.empty() && (result.back() == '\n' || result.back() == ' ')) {
18806 result.pop_back();
18807 }
18808 return result;
18809}
18810
18811} // namespace tls
18812
18813#endif // CPPHTTPLIB_MBEDTLS_SUPPORT
18814
18815/*
18816 * Group 10: TLS abstraction layer - wolfSSL backend
18817 */
18818
18819/*
18820 * wolfSSL Backend Implementation
18821 */
18822
18823#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
18824namespace tls {
18825
18826namespace impl {
18827
18828// wolfSSL session wrapper
18829struct WolfSSLSession {
18830 WOLFSSL *ssl = nullptr;
18831 socket_t sock = INVALID_SOCKET;
18832 std::string hostname; // For client: set via set_sni
18833 std::string sni_hostname; // For server: received from client via SNI callback
18834
18835 WolfSSLSession() = default;
18836
18837 ~WolfSSLSession() {
18838 if (ssl) { wolfSSL_free(ssl); }
18839 }
18840
18841 WolfSSLSession(const WolfSSLSession &) = delete;
18842 WolfSSLSession &operator=(const WolfSSLSession &) = delete;
18843};
18844
18845// Thread-local error code accessor for wolfSSL
18846inline uint64_t &wolfssl_last_error() {
18847 static thread_local uint64_t err = 0;
18848 return err;
18849}
18850
18851// Helper to map wolfSSL error to ErrorCode.
18852// ssl_error is the value from wolfSSL_get_error().
18853// raw_ret is the raw return value from the wolfSSL call (for low-level error).
18854inline ErrorCode map_wolfssl_error(WOLFSSL *ssl, int ssl_error,
18855 int &out_errno) {
18856 switch (ssl_error) {
18857 case SSL_ERROR_NONE: return ErrorCode::Success;
18858 case SSL_ERROR_WANT_READ: return ErrorCode::WantRead;
18859 case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite;
18860 case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed;
18861 case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError;
18862 default:
18863 if (ssl) {
18864 // wolfSSL stores the low-level error code as a negative value.
18865 // DOMAIN_NAME_MISMATCH (-322) indicates hostname verification failure.
18866 int low_err = ssl_error; // wolfSSL_get_error returns the low-level code
18867 if (low_err == DOMAIN_NAME_MISMATCH) {
18868 return ErrorCode::HostnameMismatch;
18869 }
18870 // Check verify result to distinguish cert verification from generic SSL
18871 // errors.
18872 long vr = wolfSSL_get_verify_result(ssl);
18873 if (vr != 0) { return ErrorCode::CertVerifyFailed; }
18874 }
18875 return ErrorCode::Fatal;
18876 }
18877}
18878
18879// WolfSSLContext constructor/destructor implementations
18880inline WolfSSLContext::WolfSSLContext() { wolfSSL_Init(); }
18881
18882inline WolfSSLContext::~WolfSSLContext() {
18883 if (ctx) { wolfSSL_CTX_free(ctx); }
18884}
18885
18886// Thread-local storage for SNI captured during handshake
18887inline std::string &wolfssl_pending_sni() {
18888 static thread_local std::string sni;
18889 return sni;
18890}
18891
18892// SNI callback for wolfSSL server to capture client's SNI hostname
18893inline int wolfssl_sni_callback(WOLFSSL *ssl, int *ret, void *exArg) {
18894 (void)ret;
18895 (void)exArg;
18896
18897 void *name_data = nullptr;
18898 unsigned short name_len =
18899 wolfSSL_SNI_GetRequest(ssl, WOLFSSL_SNI_HOST_NAME, &name_data);
18900
18901 if (name_data && name_len > 0) {
18902 wolfssl_pending_sni().assign(static_cast<const char *>(name_data),
18903 name_len);
18904 } else {
18905 wolfssl_pending_sni().clear();
18906 }
18907 return 0; // Continue regardless
18908}
18909
18910// wolfSSL verify callback wrapper
18911inline int wolfssl_verify_callback(int preverify_ok,
18912 WOLFSSL_X509_STORE_CTX *x509_ctx) {
18913 auto &callback = get_verify_callback();
18914 if (!callback) { return preverify_ok; }
18915
18916 WOLFSSL_X509 *cert = wolfSSL_X509_STORE_CTX_get_current_cert(x509_ctx);
18917 int depth = wolfSSL_X509_STORE_CTX_get_error_depth(x509_ctx);
18918 int err = wolfSSL_X509_STORE_CTX_get_error(x509_ctx);
18919
18920 // Get the WOLFSSL object from the X509_STORE_CTX
18921 WOLFSSL *ssl = static_cast<WOLFSSL *>(wolfSSL_X509_STORE_CTX_get_ex_data(
18922 x509_ctx, wolfSSL_get_ex_data_X509_STORE_CTX_idx()));
18923
18924 VerifyContext verify_ctx;
18925 verify_ctx.session = static_cast<session_t>(ssl);
18926 verify_ctx.cert = static_cast<cert_t>(cert);
18927 verify_ctx.depth = depth;
18928 verify_ctx.preverify_ok = (preverify_ok != 0);
18929 verify_ctx.error_code = static_cast<long>(err);
18930
18931 if (err != 0) {
18932 verify_ctx.error_string = wolfSSL_X509_verify_cert_error_string(err);
18933 } else {
18934 verify_ctx.error_string = nullptr;
18935 }
18936
18937 bool accepted = callback(verify_ctx);
18938 return accepted ? 1 : 0;
18939}
18940
18941inline void set_wolfssl_password_cb(WOLFSSL_CTX *ctx, const char *password) {
18942 wolfSSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast<char *>(password));
18943 wolfSSL_CTX_set_default_passwd_cb(
18944 ctx, [](char *buf, int size, int /*rwflag*/, void *userdata) -> int {
18945 auto *pwd = static_cast<const char *>(userdata);
18946 if (!pwd) return 0;
18947 auto len = static_cast<int>(strlen(pwd));
18948 if (len > size) len = size;
18949 memcpy(buf, pwd, static_cast<size_t>(len));
18950 return len;
18951 });
18952}
18953
18954} // namespace impl
18955
18956inline ctx_t create_client_context() {
18957 auto ctx = new (std::nothrow) impl::WolfSSLContext();
18958 if (!ctx) { return nullptr; }
18959
18960 ctx->is_server = false;
18961
18962 WOLFSSL_METHOD *method = wolfTLSv1_2_client_method();
18963 if (!method) {
18964 delete ctx;
18965 return nullptr;
18966 }
18967
18968 ctx->ctx = wolfSSL_CTX_new(method);
18969 if (!ctx->ctx) {
18970 delete ctx;
18971 return nullptr;
18972 }
18973
18974 // Default: verify peer certificate
18975 wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER, nullptr);
18976
18977 return static_cast<ctx_t>(ctx);
18978}
18979
18980inline ctx_t create_server_context() {
18981 auto ctx = new (std::nothrow) impl::WolfSSLContext();
18982 if (!ctx) { return nullptr; }
18983
18984 ctx->is_server = true;
18985
18986 WOLFSSL_METHOD *method = wolfTLSv1_2_server_method();
18987 if (!method) {
18988 delete ctx;
18989 return nullptr;
18990 }
18991
18992 ctx->ctx = wolfSSL_CTX_new(method);
18993 if (!ctx->ctx) {
18994 delete ctx;
18995 return nullptr;
18996 }
18997
18998 // Default: don't verify client
18999 wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, nullptr);
19000
19001 // Enable SNI on server
19002 wolfSSL_CTX_SNI_SetOptions(ctx->ctx, WOLFSSL_SNI_HOST_NAME,
19003 WOLFSSL_SNI_CONTINUE_ON_MISMATCH);
19004 wolfSSL_CTX_set_servername_callback(ctx->ctx, impl::wolfssl_sni_callback);
19005
19006 return static_cast<ctx_t>(ctx);
19007}
19008
19009inline void free_context(ctx_t ctx) {
19010 if (ctx) { delete static_cast<impl::WolfSSLContext *>(ctx); }
19011}
19012
19013inline bool set_min_version(ctx_t ctx, Version version) {
19014 if (!ctx) { return false; }
19015 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19016
19017 int min_ver = WOLFSSL_TLSV1_2;
19018 if (version >= Version::TLS1_3) { min_ver = WOLFSSL_TLSV1_3; }
19019
19020 return wolfSSL_CTX_SetMinVersion(wctx->ctx, min_ver) == WOLFSSL_SUCCESS;
19021}
19022
19023inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
19024 if (!ctx || !pem) { return false; }
19025 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19026
19027 int ret = wolfSSL_CTX_load_verify_buffer(
19028 wctx->ctx, reinterpret_cast<const unsigned char *>(pem),
19029 static_cast<long>(len), SSL_FILETYPE_PEM);
19030 if (ret != SSL_SUCCESS) {
19031 impl::wolfssl_last_error() =
19032 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19033 return false;
19034 }
19035 wctx->ca_pem_data_.append(pem, len);
19036 return true;
19037}
19038
19039inline bool load_ca_file(ctx_t ctx, const char *file_path) {
19040 if (!ctx || !file_path) { return false; }
19041 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19042
19043 int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, file_path, nullptr);
19044 if (ret != SSL_SUCCESS) {
19045 impl::wolfssl_last_error() =
19046 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19047 return false;
19048 }
19049 return true;
19050}
19051
19052inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
19053 if (!ctx || !dir_path) { return false; }
19054 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19055
19056 int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, dir_path);
19057 // wolfSSL may fail if the directory doesn't contain properly hashed certs.
19058 // Unlike OpenSSL which lazily loads certs from directories, wolfSSL scans
19059 // immediately. Return true even on failure since the CA file may have
19060 // already been loaded, matching OpenSSL's lenient behavior.
19061 (void)ret;
19062 return true;
19063}
19064
19065inline bool load_system_certs(ctx_t ctx) {
19066 if (!ctx) { return false; }
19067 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19068 bool loaded = false;
19069
19070#ifdef _WIN32
19071 loaded = impl::enumerate_windows_system_certs(
19072 [&](const unsigned char *data, size_t len) {
19073 return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data,
19074 static_cast<long>(len),
19075 SSL_FILETYPE_ASN1) == SSL_SUCCESS;
19076 });
19077#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
19078 loaded = impl::enumerate_macos_keychain_certs(
19079 [&](const unsigned char *data, size_t len) {
19080 return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data,
19081 static_cast<long>(len),
19082 SSL_FILETYPE_ASN1) == SSL_SUCCESS;
19083 });
19084#else
19085 for (auto path = impl::system_ca_paths(); *path; ++path) {
19086 if (wolfSSL_CTX_load_verify_locations(wctx->ctx, *path, nullptr) ==
19087 SSL_SUCCESS) {
19088 loaded = true;
19089 break;
19090 }
19091 }
19092
19093 if (!loaded) {
19094 for (auto dir = impl::system_ca_dirs(); *dir; ++dir) {
19095 if (wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, *dir) ==
19096 SSL_SUCCESS) {
19097 loaded = true;
19098 break;
19099 }
19100 }
19101 }
19102#endif
19103
19104 return loaded;
19105}
19106
19107inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
19108 const char *password) {
19109 if (!ctx || !cert || !key) { return false; }
19110 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19111
19112 // Load certificate
19113 int ret = wolfSSL_CTX_use_certificate_buffer(
19114 wctx->ctx, reinterpret_cast<const unsigned char *>(cert),
19115 static_cast<long>(strlen(cert)), SSL_FILETYPE_PEM);
19116 if (ret != SSL_SUCCESS) {
19117 impl::wolfssl_last_error() =
19118 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19119 return false;
19120 }
19121
19122 // Set password callback if password is provided
19123 if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
19124
19125 // Load private key
19126 ret = wolfSSL_CTX_use_PrivateKey_buffer(
19127 wctx->ctx, reinterpret_cast<const unsigned char *>(key),
19128 static_cast<long>(strlen(key)), SSL_FILETYPE_PEM);
19129 if (ret != SSL_SUCCESS) {
19130 impl::wolfssl_last_error() =
19131 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19132 return false;
19133 }
19134
19135 // Verify that the certificate and private key match
19136 return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS;
19137}
19138
19139inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
19140 const char *key_path, const char *password) {
19141 if (!ctx || !cert_path || !key_path) { return false; }
19142 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19143
19144 // Load certificate file
19145 int ret =
19146 wolfSSL_CTX_use_certificate_file(wctx->ctx, cert_path, SSL_FILETYPE_PEM);
19147 if (ret != SSL_SUCCESS) {
19148 impl::wolfssl_last_error() =
19149 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19150 return false;
19151 }
19152
19153 // Set password callback if password is provided
19154 if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
19155
19156 // Load private key file
19157 ret = wolfSSL_CTX_use_PrivateKey_file(wctx->ctx, key_path, SSL_FILETYPE_PEM);
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
19164 // Verify that the certificate and private key match
19165 return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS;
19166}
19167
19168inline void set_verify_client(ctx_t ctx, bool require) {
19169 if (!ctx) { return; }
19170 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19171 wctx->verify_client = require;
19172 if (require) {
19173 wolfSSL_CTX_set_verify(
19174 wctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
19175 wctx->has_verify_callback ? impl::wolfssl_verify_callback : nullptr);
19176 } else {
19177 if (wctx->has_verify_callback) {
19178 wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
19179 impl::wolfssl_verify_callback);
19180 } else {
19181 wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_NONE, nullptr);
19182 }
19183 }
19184}
19185
19186inline session_t create_session(ctx_t ctx, socket_t sock) {
19187 if (!ctx || sock == INVALID_SOCKET) { return nullptr; }
19188 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19189
19190 auto session = new (std::nothrow) impl::WolfSSLSession();
19191 if (!session) { return nullptr; }
19192
19193 session->sock = sock;
19194 session->ssl = wolfSSL_new(wctx->ctx);
19195 if (!session->ssl) {
19196 impl::wolfssl_last_error() =
19197 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19198 delete session;
19199 return nullptr;
19200 }
19201
19202 wolfSSL_set_fd(session->ssl, static_cast<int>(sock));
19203
19204 return static_cast<session_t>(session);
19205}
19206
19207inline void free_session(session_t session) {
19208 if (session) { delete static_cast<impl::WolfSSLSession *>(session); }
19209}
19210
19211inline bool set_sni(session_t session, const char *hostname) {
19212 if (!session || !hostname) { return false; }
19213 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19214
19215 int ret = wolfSSL_UseSNI(wsession->ssl, WOLFSSL_SNI_HOST_NAME, hostname,
19216 static_cast<word16>(strlen(hostname)));
19217 if (ret != WOLFSSL_SUCCESS) {
19218 impl::wolfssl_last_error() =
19219 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19220 return false;
19221 }
19222
19223 // Also set hostname for verification
19224 wolfSSL_check_domain_name(wsession->ssl, hostname);
19225
19226 wsession->hostname = hostname;
19227 return true;
19228}
19229
19230inline bool set_hostname(session_t session, const char *hostname) {
19231 // In wolfSSL, set_hostname also sets up hostname verification
19232 return set_sni(session, hostname);
19233}
19234
19235inline TlsError connect(session_t session) {
19236 TlsError err;
19237 if (!session) {
19238 err.code = ErrorCode::Fatal;
19239 return err;
19240 }
19241
19242 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19243 int ret = wolfSSL_connect(wsession->ssl);
19244
19245 if (ret == SSL_SUCCESS) {
19246 err.code = ErrorCode::Success;
19247 } else {
19248 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19249 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19250 err.backend_code = static_cast<uint64_t>(ssl_error);
19251 impl::wolfssl_last_error() = err.backend_code;
19252 }
19253
19254 return err;
19255}
19256
19257inline TlsError accept(session_t session) {
19258 TlsError err;
19259 if (!session) {
19260 err.code = ErrorCode::Fatal;
19261 return err;
19262 }
19263
19264 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19265 int ret = wolfSSL_accept(wsession->ssl);
19266
19267 if (ret == SSL_SUCCESS) {
19268 err.code = ErrorCode::Success;
19269 // Capture SNI from thread-local storage after successful handshake
19270 wsession->sni_hostname = std::move(impl::wolfssl_pending_sni());
19271 impl::wolfssl_pending_sni().clear();
19272 } else {
19273 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19274 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19275 err.backend_code = static_cast<uint64_t>(ssl_error);
19276 impl::wolfssl_last_error() = err.backend_code;
19277 }
19278
19279 return err;
19280}
19281
19282inline bool connect_nonblocking(session_t session, socket_t sock,
19283 time_t timeout_sec, time_t timeout_usec,
19284 TlsError *err) {
19285 if (!session) {
19286 if (err) { err->code = ErrorCode::Fatal; }
19287 return false;
19288 }
19289
19290 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19291
19292 // Set socket to non-blocking mode
19293 detail::set_nonblocking(sock, true);
19294 auto cleanup =
19295 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19296
19297 int ret;
19298 while ((ret = wolfSSL_connect(wsession->ssl)) != SSL_SUCCESS) {
19299 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19300 if (ssl_error == SSL_ERROR_WANT_READ) {
19301 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
19302 continue;
19303 }
19304 } else if (ssl_error == SSL_ERROR_WANT_WRITE) {
19305 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
19306 continue;
19307 }
19308 }
19309
19310 // Error or timeout
19311 if (err) {
19312 err->code =
19313 impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno);
19314 err->backend_code = static_cast<uint64_t>(ssl_error);
19315 }
19316 impl::wolfssl_last_error() = static_cast<uint64_t>(ssl_error);
19317 return false;
19318 }
19319
19320 if (err) { err->code = ErrorCode::Success; }
19321 return true;
19322}
19323
19324inline bool accept_nonblocking(session_t session, socket_t sock,
19325 time_t timeout_sec, time_t timeout_usec,
19326 TlsError *err) {
19327 if (!session) {
19328 if (err) { err->code = ErrorCode::Fatal; }
19329 return false;
19330 }
19331
19332 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19333
19334 // Set socket to non-blocking mode
19335 detail::set_nonblocking(sock, true);
19336 auto cleanup =
19337 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19338
19339 int ret;
19340 while ((ret = wolfSSL_accept(wsession->ssl)) != SSL_SUCCESS) {
19341 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19342 if (ssl_error == SSL_ERROR_WANT_READ) {
19343 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
19344 continue;
19345 }
19346 } else if (ssl_error == SSL_ERROR_WANT_WRITE) {
19347 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
19348 continue;
19349 }
19350 }
19351
19352 // Error or timeout
19353 if (err) {
19354 err->code =
19355 impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno);
19356 err->backend_code = static_cast<uint64_t>(ssl_error);
19357 }
19358 impl::wolfssl_last_error() = static_cast<uint64_t>(ssl_error);
19359 return false;
19360 }
19361
19362 if (err) { err->code = ErrorCode::Success; }
19363
19364 // Capture SNI from thread-local storage after successful handshake
19365 wsession->sni_hostname = std::move(impl::wolfssl_pending_sni());
19366 impl::wolfssl_pending_sni().clear();
19367
19368 return true;
19369}
19370
19371inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
19372 if (!session || !buf) {
19373 err.code = ErrorCode::Fatal;
19374 return -1;
19375 }
19376
19377 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19378 int ret = wolfSSL_read(wsession->ssl, buf, static_cast<int>(len));
19379
19380 if (ret > 0) {
19381 err.code = ErrorCode::Success;
19382 return static_cast<ssize_t>(ret);
19383 }
19384
19385 if (ret == 0) {
19386 err.code = ErrorCode::PeerClosed;
19387 return 0;
19388 }
19389
19390 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19391 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19392 err.backend_code = static_cast<uint64_t>(ssl_error);
19393 impl::wolfssl_last_error() = err.backend_code;
19394 return -1;
19395}
19396
19397inline ssize_t write(session_t session, const void *buf, size_t len,
19398 TlsError &err) {
19399 if (!session || !buf) {
19400 err.code = ErrorCode::Fatal;
19401 return -1;
19402 }
19403
19404 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19405 int ret = wolfSSL_write(wsession->ssl, buf, static_cast<int>(len));
19406
19407 if (ret > 0) {
19408 err.code = ErrorCode::Success;
19409 return static_cast<ssize_t>(ret);
19410 }
19411
19412 // wolfSSL_write returns 0 when the peer has sent a close_notify.
19413 // Treat this as an error (return -1) so callers don't spin in a
19414 // write loop adding zero to the offset.
19415 if (ret == 0) {
19416 err.code = ErrorCode::PeerClosed;
19417 return -1;
19418 }
19419
19420 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19421 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19422 err.backend_code = static_cast<uint64_t>(ssl_error);
19423 impl::wolfssl_last_error() = err.backend_code;
19424 return -1;
19425}
19426
19427inline int pending(const_session_t session) {
19428 if (!session) { return 0; }
19429 auto wsession =
19430 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19431 return wolfSSL_pending(wsession->ssl);
19432}
19433
19434inline void shutdown(session_t session, bool graceful) {
19435 if (!session) { return; }
19436 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19437
19438 if (graceful) {
19439 int ret;
19440 int attempts = 0;
19441 while ((ret = wolfSSL_shutdown(wsession->ssl)) != SSL_SUCCESS &&
19442 attempts < 3) {
19443 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19444 if (ssl_error != SSL_ERROR_WANT_READ &&
19445 ssl_error != SSL_ERROR_WANT_WRITE) {
19446 break;
19447 }
19448 attempts++;
19449 }
19450 } else {
19451 wolfSSL_shutdown(wsession->ssl);
19452 }
19453}
19454
19455inline bool is_peer_closed(session_t session, socket_t sock) {
19456 if (!session || sock == INVALID_SOCKET) { return true; }
19457 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19458
19459 // Check if there's already decrypted data available
19460 if (wolfSSL_pending(wsession->ssl) > 0) { return false; }
19461
19462 // Set socket to non-blocking to avoid blocking on read
19463 detail::set_nonblocking(sock, true);
19464 auto cleanup =
19465 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19466
19467 // Peek 1 byte to check connection status without consuming data
19468 unsigned char buf;
19469 int ret = wolfSSL_peek(wsession->ssl, &buf, 1);
19470
19471 // If we got data or WANT_READ (would block), connection is alive
19472 if (ret > 0) { return false; }
19473
19474 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19475 if (ssl_error == SSL_ERROR_WANT_READ) { return false; }
19476
19477 return ssl_error == SSL_ERROR_ZERO_RETURN || ssl_error == SSL_ERROR_SYSCALL ||
19478 ret == 0;
19479}
19480
19481inline cert_t get_peer_cert(const_session_t session) {
19482 if (!session) { return nullptr; }
19483 auto wsession =
19484 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19485
19486 WOLFSSL_X509 *cert = wolfSSL_get_peer_certificate(wsession->ssl);
19487 return static_cast<cert_t>(cert);
19488}
19489
19490inline void free_cert(cert_t cert) {
19491 if (cert) { wolfSSL_X509_free(static_cast<WOLFSSL_X509 *>(cert)); }
19492}
19493
19494inline bool verify_hostname(cert_t cert, const char *hostname) {
19495 if (!cert || !hostname) { return false; }
19496 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19497 std::string host_str(hostname);
19498
19499 // Check if hostname is an IP address
19500 bool is_ip = impl::is_ipv4_address(host_str);
19501 unsigned char ip_bytes[4];
19502 if (is_ip) { impl::parse_ipv4(host_str, ip_bytes); }
19503
19504 // Check Subject Alternative Names
19505 auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
19506 wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
19507
19508 if (san_names) {
19509 int san_count = wolfSSL_sk_num(san_names);
19510 for (int i = 0; i < san_count; i++) {
19511 auto *names =
19512 static_cast<WOLFSSL_GENERAL_NAME *>(wolfSSL_sk_value(san_names, i));
19513 if (!names) continue;
19514
19515 if (!is_ip && names->type == WOLFSSL_GEN_DNS) {
19516 // DNS name
19517 unsigned char *dns_name = nullptr;
19518 int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, names->d.dNSName);
19519 if (dns_name && dns_len > 0) {
19520 std::string san_name(reinterpret_cast<char *>(dns_name),
19521 static_cast<size_t>(dns_len));
19522 XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL);
19523 if (detail::match_hostname(san_name, host_str)) {
19524 wolfSSL_sk_free(san_names);
19525 return true;
19526 }
19527 }
19528 } else if (is_ip && names->type == WOLFSSL_GEN_IPADD) {
19529 // IP address
19530 unsigned char *ip_data = wolfSSL_ASN1_STRING_data(names->d.iPAddress);
19531 int ip_len = wolfSSL_ASN1_STRING_length(names->d.iPAddress);
19532 if (ip_data && ip_len == 4 && memcmp(ip_data, ip_bytes, 4) == 0) {
19533 wolfSSL_sk_free(san_names);
19534 return true;
19535 }
19536 }
19537 }
19538 wolfSSL_sk_free(san_names);
19539 }
19540
19541 // Fallback: Check Common Name (CN) in subject
19542 WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
19543 if (subject) {
19544 char cn[256] = {};
19545 int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
19546 sizeof(cn));
19547 if (cn_len > 0) {
19548 std::string cn_str(cn, static_cast<size_t>(cn_len));
19549 if (detail::match_hostname(cn_str, host_str)) { return true; }
19550 }
19551 }
19552
19553 return false;
19554}
19555
19556inline uint64_t hostname_mismatch_code() {
19557 return static_cast<uint64_t>(DOMAIN_NAME_MISMATCH);
19558}
19559
19560inline long get_verify_result(const_session_t session) {
19561 if (!session) { return -1; }
19562 auto wsession =
19563 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19564 long result = wolfSSL_get_verify_result(wsession->ssl);
19565 return result;
19566}
19567
19568inline std::string get_cert_subject_cn(cert_t cert) {
19569 if (!cert) return "";
19570 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19571
19572 WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
19573 if (!subject) return "";
19574
19575 char cn[256] = {};
19576 int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
19577 sizeof(cn));
19578 if (cn_len <= 0) return "";
19579 return std::string(cn, static_cast<size_t>(cn_len));
19580}
19581
19582inline std::string get_cert_issuer_name(cert_t cert) {
19583 if (!cert) return "";
19584 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19585
19586 WOLFSSL_X509_NAME *issuer = wolfSSL_X509_get_issuer_name(x509);
19587 if (!issuer) return "";
19588
19589 char *name_str = wolfSSL_X509_NAME_oneline(issuer, nullptr, 0);
19590 if (!name_str) return "";
19591
19592 std::string result(name_str);
19593 XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL);
19594 return result;
19595}
19596
19597inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
19598 sans.clear();
19599 if (!cert) return false;
19600 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19601
19602 auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
19603 wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
19604 if (!san_names) return true; // No SANs is not an error
19605
19606 int count = wolfSSL_sk_num(san_names);
19607 for (int i = 0; i < count; i++) {
19608 auto *name =
19609 static_cast<WOLFSSL_GENERAL_NAME *>(wolfSSL_sk_value(san_names, i));
19610 if (!name) continue;
19611
19612 SanEntry entry;
19613 switch (name->type) {
19614 case WOLFSSL_GEN_DNS: {
19615 entry.type = SanType::DNS;
19616 unsigned char *dns_name = nullptr;
19617 int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, name->d.dNSName);
19618 if (dns_name && dns_len > 0) {
19619 entry.value = std::string(reinterpret_cast<char *>(dns_name),
19620 static_cast<size_t>(dns_len));
19621 XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL);
19622 }
19623 break;
19624 }
19625 case WOLFSSL_GEN_IPADD: {
19626 entry.type = SanType::IP;
19627 unsigned char *ip_data = wolfSSL_ASN1_STRING_data(name->d.iPAddress);
19628 int ip_len = wolfSSL_ASN1_STRING_length(name->d.iPAddress);
19629 if (ip_data && ip_len == 4) {
19630 char buf[16];
19631 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", ip_data[0], ip_data[1],
19632 ip_data[2], ip_data[3]);
19633 entry.value = buf;
19634 } else if (ip_data && ip_len == 16) {
19635 char buf[64];
19636 snprintf(buf, sizeof(buf),
19637 "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
19638 "%02x%02x:%02x%02x:%02x%02x:%02x%02x",
19639 ip_data[0], ip_data[1], ip_data[2], ip_data[3], ip_data[4],
19640 ip_data[5], ip_data[6], ip_data[7], ip_data[8], ip_data[9],
19641 ip_data[10], ip_data[11], ip_data[12], ip_data[13],
19642 ip_data[14], ip_data[15]);
19643 entry.value = buf;
19644 }
19645 break;
19646 }
19647 case WOLFSSL_GEN_EMAIL:
19648 entry.type = SanType::EMAIL;
19649 {
19650 unsigned char *email = nullptr;
19651 int email_len = wolfSSL_ASN1_STRING_to_UTF8(&email, name->d.rfc822Name);
19652 if (email && email_len > 0) {
19653 entry.value = std::string(reinterpret_cast<char *>(email),
19654 static_cast<size_t>(email_len));
19655 XFREE(email, nullptr, DYNAMIC_TYPE_OPENSSL);
19656 }
19657 }
19658 break;
19659 case WOLFSSL_GEN_URI:
19660 entry.type = SanType::URI;
19661 {
19662 unsigned char *uri = nullptr;
19663 int uri_len = wolfSSL_ASN1_STRING_to_UTF8(
19664 &uri, name->d.uniformResourceIdentifier);
19665 if (uri && uri_len > 0) {
19666 entry.value = std::string(reinterpret_cast<char *>(uri),
19667 static_cast<size_t>(uri_len));
19668 XFREE(uri, nullptr, DYNAMIC_TYPE_OPENSSL);
19669 }
19670 }
19671 break;
19672 default: entry.type = SanType::OTHER; break;
19673 }
19674
19675 if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
19676 }
19677 wolfSSL_sk_free(san_names);
19678 return true;
19679}
19680
19681inline bool get_cert_validity(cert_t cert, time_t &not_before,
19682 time_t &not_after) {
19683 if (!cert) return false;
19684 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19685
19686 const WOLFSSL_ASN1_TIME *nb = wolfSSL_X509_get_notBefore(x509);
19687 const WOLFSSL_ASN1_TIME *na = wolfSSL_X509_get_notAfter(x509);
19688
19689 if (!nb || !na) return false;
19690
19691 // wolfSSL_ASN1_TIME_to_tm is available
19692 struct tm tm_nb = {}, tm_na = {};
19693 if (wolfSSL_ASN1_TIME_to_tm(nb, &tm_nb) != WOLFSSL_SUCCESS) return false;
19694 if (wolfSSL_ASN1_TIME_to_tm(na, &tm_na) != WOLFSSL_SUCCESS) return false;
19695
19696#ifdef _WIN32
19697 not_before = _mkgmtime(&tm_nb);
19698 not_after = _mkgmtime(&tm_na);
19699#else
19700 not_before = timegm(&tm_nb);
19701 not_after = timegm(&tm_na);
19702#endif
19703 return true;
19704}
19705
19706inline std::string get_cert_serial(cert_t cert) {
19707 if (!cert) return "";
19708 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19709
19710 WOLFSSL_ASN1_INTEGER *serial_asn1 = wolfSSL_X509_get_serialNumber(x509);
19711 if (!serial_asn1) return "";
19712
19713 // Get the serial number data
19714 int len = serial_asn1->length;
19715 unsigned char *data = serial_asn1->data;
19716 if (!data || len <= 0) return "";
19717
19718 std::string result;
19719 result.reserve(static_cast<size_t>(len) * 2);
19720 for (int i = 0; i < len; i++) {
19721 char hex[3];
19722 snprintf(hex, sizeof(hex), "%02X", data[i]);
19723 result += hex;
19724 }
19725 return result;
19726}
19727
19728inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
19729 if (!cert) return false;
19730 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19731
19732 int der_len = 0;
19733 const unsigned char *der_data = wolfSSL_X509_get_der(x509, &der_len);
19734 if (!der_data || der_len <= 0) return false;
19735
19736 der.assign(der_data, der_data + der_len);
19737 return true;
19738}
19739
19740inline const char *get_sni(const_session_t session) {
19741 if (!session) return nullptr;
19742 auto wsession = static_cast<const impl::WolfSSLSession *>(session);
19743
19744 // For server: return SNI received from client during handshake
19745 if (!wsession->sni_hostname.empty()) {
19746 return wsession->sni_hostname.c_str();
19747 }
19748
19749 // For client: return the hostname set via set_sni
19750 if (!wsession->hostname.empty()) { return wsession->hostname.c_str(); }
19751
19752 return nullptr;
19753}
19754
19755inline uint64_t peek_error() {
19756 return static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19757}
19758
19759inline uint64_t get_error() {
19760 uint64_t err = impl::wolfssl_last_error();
19761 impl::wolfssl_last_error() = 0;
19762 return err;
19763}
19764
19765inline std::string error_string(uint64_t code) {
19766 char buf[256];
19767 wolfSSL_ERR_error_string(static_cast<unsigned long>(code), buf);
19768 return std::string(buf);
19769}
19770
19771inline ca_store_t create_ca_store(const char *pem, size_t len) {
19772 if (!pem || len == 0) { return nullptr; }
19773 // Validate by attempting to load into a temporary ctx
19774 WOLFSSL_CTX *tmp_ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
19775 if (!tmp_ctx) { return nullptr; }
19776 int ret = wolfSSL_CTX_load_verify_buffer(
19777 tmp_ctx, reinterpret_cast<const unsigned char *>(pem),
19778 static_cast<long>(len), SSL_FILETYPE_PEM);
19779 wolfSSL_CTX_free(tmp_ctx);
19780 if (ret != SSL_SUCCESS) { return nullptr; }
19781 return static_cast<ca_store_t>(
19782 new impl::WolfSSLCAStore{std::string(pem, len)});
19783}
19784
19785inline void free_ca_store(ca_store_t store) {
19786 delete static_cast<impl::WolfSSLCAStore *>(store);
19787}
19788
19789inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
19790 if (!ctx || !store) { return false; }
19791 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
19792 auto *ca = static_cast<impl::WolfSSLCAStore *>(store);
19793 int ret = wolfSSL_CTX_load_verify_buffer(
19794 wctx->ctx, reinterpret_cast<const unsigned char *>(ca->pem_data.data()),
19795 static_cast<long>(ca->pem_data.size()), SSL_FILETYPE_PEM);
19796 if (ret == SSL_SUCCESS) { wctx->ca_pem_data_ += ca->pem_data; }
19797 return ret == SSL_SUCCESS;
19798}
19799
19800inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
19801 certs.clear();
19802 if (!ctx) { return 0; }
19803 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
19804 if (wctx->ca_pem_data_.empty()) { return 0; }
19805
19806 const std::string &pem = wctx->ca_pem_data_;
19807 const std::string begin_marker = "-----BEGIN CERTIFICATE-----";
19808 const std::string end_marker = "-----END CERTIFICATE-----";
19809 size_t pos = 0;
19810 while ((pos = pem.find(begin_marker, pos)) != std::string::npos) {
19811 size_t end_pos = pem.find(end_marker, pos);
19812 if (end_pos == std::string::npos) { break; }
19813 end_pos += end_marker.size();
19814 std::string cert_pem = pem.substr(pos, end_pos - pos);
19815 WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer(
19816 reinterpret_cast<const unsigned char *>(cert_pem.data()),
19817 static_cast<int>(cert_pem.size()), WOLFSSL_FILETYPE_PEM);
19818 if (x509) { certs.push_back(static_cast<cert_t>(x509)); }
19819 pos = end_pos;
19820 }
19821 return certs.size();
19822}
19823
19824inline std::vector<std::string> get_ca_names(ctx_t ctx) {
19825 std::vector<std::string> names;
19826 if (!ctx) { return names; }
19827 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
19828 if (wctx->ca_pem_data_.empty()) { return names; }
19829
19830 const std::string &pem = wctx->ca_pem_data_;
19831 const std::string begin_marker = "-----BEGIN CERTIFICATE-----";
19832 const std::string end_marker = "-----END CERTIFICATE-----";
19833 size_t pos = 0;
19834 while ((pos = pem.find(begin_marker, pos)) != std::string::npos) {
19835 size_t end_pos = pem.find(end_marker, pos);
19836 if (end_pos == std::string::npos) { break; }
19837 end_pos += end_marker.size();
19838 std::string cert_pem = pem.substr(pos, end_pos - pos);
19839 WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer(
19840 reinterpret_cast<const unsigned char *>(cert_pem.data()),
19841 static_cast<int>(cert_pem.size()), WOLFSSL_FILETYPE_PEM);
19842 if (x509) {
19843 WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
19844 if (subject) {
19845 char *name_str = wolfSSL_X509_NAME_oneline(subject, nullptr, 0);
19846 if (name_str) {
19847 names.push_back(name_str);
19848 XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL);
19849 }
19850 }
19851 wolfSSL_X509_free(x509);
19852 }
19853 pos = end_pos;
19854 }
19855 return names;
19856}
19857
19858inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
19859 const char *key_pem, const char *password) {
19860 if (!ctx || !cert_pem || !key_pem) { return false; }
19861 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
19862
19863 // Load new certificate
19864 int ret = wolfSSL_CTX_use_certificate_buffer(
19865 wctx->ctx, reinterpret_cast<const unsigned char *>(cert_pem),
19866 static_cast<long>(strlen(cert_pem)), SSL_FILETYPE_PEM);
19867 if (ret != SSL_SUCCESS) {
19868 impl::wolfssl_last_error() =
19869 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19870 return false;
19871 }
19872
19873 // Set password if provided
19874 if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
19875
19876 // Load new private key
19877 ret = wolfSSL_CTX_use_PrivateKey_buffer(
19878 wctx->ctx, reinterpret_cast<const unsigned char *>(key_pem),
19879 static_cast<long>(strlen(key_pem)), SSL_FILETYPE_PEM);
19880 if (ret != SSL_SUCCESS) {
19881 impl::wolfssl_last_error() =
19882 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19883 return false;
19884 }
19885
19886 return true;
19887}
19888
19889inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
19890 if (!ctx || !ca_pem) { return false; }
19891 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
19892
19893 int ret = wolfSSL_CTX_load_verify_buffer(
19894 wctx->ctx, reinterpret_cast<const unsigned char *>(ca_pem),
19895 static_cast<long>(strlen(ca_pem)), SSL_FILETYPE_PEM);
19896 if (ret != SSL_SUCCESS) {
19897 impl::wolfssl_last_error() =
19898 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19899 return false;
19900 }
19901 return true;
19902}
19903
19904inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
19905 if (!ctx) { return false; }
19906 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
19907
19908 impl::get_verify_callback() = std::move(callback);
19909 wctx->has_verify_callback = static_cast<bool>(impl::get_verify_callback());
19910
19911 if (wctx->has_verify_callback) {
19912 wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
19913 impl::wolfssl_verify_callback);
19914 } else {
19915 wolfSSL_CTX_set_verify(
19916 wctx->ctx,
19917 wctx->verify_client
19918 ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
19919 : SSL_VERIFY_NONE,
19920 nullptr);
19921 }
19922 return true;
19923}
19924
19925inline long get_verify_error(const_session_t session) {
19926 if (!session) { return -1; }
19927 auto *wsession =
19928 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19929 return wolfSSL_get_verify_result(wsession->ssl);
19930}
19931
19932inline std::string verify_error_string(long error_code) {
19933 if (error_code == 0) { return ""; }
19934 const char *str =
19935 wolfSSL_X509_verify_cert_error_string(static_cast<int>(error_code));
19936 return str ? std::string(str) : std::string();
19937}
19938
19939} // namespace tls
19940
19941#endif // CPPHTTPLIB_WOLFSSL_SUPPORT
19942
19943// WebSocket implementation
19944namespace ws {
19945
19946inline bool WebSocket::send_frame(Opcode op, const char *data, size_t len,
19947 bool fin) {
19948 std::lock_guard<std::mutex> lock(write_mutex_);
19949 if (closed_) { return false; }
19950 return detail::write_websocket_frame(strm_, op, data, len, fin, !is_server_);
19951}
19952
19953inline ReadResult WebSocket::read(std::string &msg) {
19954 while (!closed_) {
19955 Opcode opcode;
19956 std::string payload;
19957 bool fin;
19958
19959 if (!impl::read_websocket_frame(strm_, opcode, payload, fin, is_server_,
19961 closed_ = true;
19962 return Fail;
19963 }
19964
19965 switch (opcode) {
19966 case Opcode::Ping: {
19967 std::lock_guard<std::mutex> lock(write_mutex_);
19968 detail::write_websocket_frame(strm_, Opcode::Pong, payload.data(),
19969 payload.size(), true, !is_server_);
19970 continue;
19971 }
19972 case Opcode::Pong: continue;
19973 case Opcode::Close: {
19974 if (!closed_.exchange(true)) {
19975 // Echo close frame back
19976 std::lock_guard<std::mutex> lock(write_mutex_);
19977 detail::write_websocket_frame(strm_, Opcode::Close, payload.data(),
19978 payload.size(), true, !is_server_);
19979 }
19980 return Fail;
19981 }
19982 case Opcode::Text:
19983 case Opcode::Binary: {
19984 auto result = opcode == Opcode::Text ? Text : Binary;
19985 msg = std::move(payload);
19986
19987 // Handle fragmentation
19988 if (!fin) {
19989 while (true) {
19990 Opcode cont_opcode;
19991 std::string cont_payload;
19992 bool cont_fin;
19994 strm_, cont_opcode, cont_payload, cont_fin, is_server_,
19996 closed_ = true;
19997 return Fail;
19998 }
19999 if (cont_opcode == Opcode::Ping) {
20000 std::lock_guard<std::mutex> lock(write_mutex_);
20002 strm_, Opcode::Pong, cont_payload.data(), cont_payload.size(),
20003 true, !is_server_);
20004 continue;
20005 }
20006 if (cont_opcode == Opcode::Pong) { continue; }
20007 if (cont_opcode == Opcode::Close) {
20008 if (!closed_.exchange(true)) {
20009 std::lock_guard<std::mutex> lock(write_mutex_);
20011 strm_, Opcode::Close, cont_payload.data(),
20012 cont_payload.size(), true, !is_server_);
20013 }
20014 return Fail;
20015 }
20016 // RFC 6455: continuation frames must use opcode 0x0
20017 if (cont_opcode != Opcode::Continuation) {
20018 closed_ = true;
20019 return Fail;
20020 }
20021 msg += cont_payload;
20022 if (msg.size() > CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH) {
20023 closed_ = true;
20024 return Fail;
20025 }
20026 if (cont_fin) { break; }
20027 }
20028 }
20029 // RFC 6455 Section 5.6: text frames must contain valid UTF-8
20030 if (result == Text && !impl::is_valid_utf8(msg)) {
20031 close(CloseStatus::InvalidPayload, "invalid UTF-8");
20032 return Fail;
20033 }
20034 return result;
20035 }
20036 default: closed_ = true; return Fail;
20037 }
20038 }
20039 return Fail;
20040}
20041
20042inline bool WebSocket::send(const std::string &data) {
20043 return send_frame(Opcode::Text, data.data(), data.size());
20044}
20045
20046inline bool WebSocket::send(const char *data, size_t len) {
20047 return send_frame(Opcode::Binary, data, len);
20048}
20049
20050inline void WebSocket::close(CloseStatus status, const std::string &reason) {
20051 if (closed_.exchange(true)) { return; }
20052 ping_cv_.notify_all();
20053 std::string payload;
20054 auto code = static_cast<uint16_t>(status);
20055 payload.push_back(static_cast<char>((code >> 8) & 0xFF));
20056 payload.push_back(static_cast<char>(code & 0xFF));
20057 // RFC 6455 Section 5.5: control frame payload must not exceed 125 bytes
20058 // Close frame has 2-byte status code, so reason is limited to 123 bytes
20059 payload += reason.substr(0, 123);
20060 {
20061 std::lock_guard<std::mutex> lock(write_mutex_);
20062 detail::write_websocket_frame(strm_, Opcode::Close, payload.data(),
20063 payload.size(), true, !is_server_);
20064 }
20065
20066 // RFC 6455 Section 7.1.1: after sending a Close frame, wait for the peer's
20067 // Close response before closing the TCP connection. Use a short timeout to
20068 // avoid hanging if the peer doesn't respond.
20069 strm_.set_read_timeout(CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND, 0);
20070 Opcode op;
20071 std::string resp;
20072 bool fin;
20073 while (impl::read_websocket_frame(strm_, op, resp, fin, is_server_, 125)) {
20074 if (op == Opcode::Close) { break; }
20075 }
20076}
20077
20079 {
20080 std::lock_guard<std::mutex> lock(ping_mutex_);
20081 closed_ = true;
20082 }
20083 ping_cv_.notify_all();
20084 if (ping_thread_.joinable()) { ping_thread_.join(); }
20085}
20086
20087inline void WebSocket::start_heartbeat() {
20088 if (ping_interval_sec_ == 0) { return; }
20089 ping_thread_ = std::thread([this]() {
20090 std::unique_lock<std::mutex> lock(ping_mutex_);
20091 while (!closed_) {
20092 ping_cv_.wait_for(lock, std::chrono::seconds(ping_interval_sec_));
20093 if (closed_) { break; }
20094 lock.unlock();
20095 if (!send_frame(Opcode::Ping, nullptr, 0)) {
20096 closed_ = true;
20097 break;
20098 }
20099 lock.lock();
20100 }
20101 });
20102}
20103
20104inline const Request &WebSocket::request() const { return req_; }
20105
20106inline bool WebSocket::is_open() const { return !closed_; }
20107
20108// WebSocketClient implementation
20110 const std::string &scheme_host_port_path, const Headers &headers)
20111 : headers_(headers) {
20112 const static std::regex re(
20113 R"(([a-z]+):\/\/(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?(\/.*))");
20114
20115 std::smatch m;
20116 if (std::regex_match(scheme_host_port_path, m, re)) {
20117 auto scheme = m[1].str();
20118
20119#ifdef CPPHTTPLIB_SSL_ENABLED
20120 if (scheme != "ws" && scheme != "wss") {
20121#else
20122 if (scheme != "ws") {
20123#endif
20124#ifndef CPPHTTPLIB_NO_EXCEPTIONS
20125 std::string msg = "'" + scheme + "' scheme is not supported.";
20126 throw std::invalid_argument(msg);
20127#endif
20128 return;
20129 }
20130
20131 auto is_ssl = scheme == "wss";
20132
20133 host_ = m[2].str();
20134 if (host_.empty()) { host_ = m[3].str(); }
20135
20136 auto port_str = m[4].str();
20137 port_ = is_ssl ? 443 : 80;
20138 if (!port_str.empty() && !detail::parse_port(port_str, port_)) { return; }
20139
20140 path_ = m[5].str();
20141
20142#ifdef CPPHTTPLIB_SSL_ENABLED
20143 is_ssl_ = is_ssl;
20144#else
20145 if (is_ssl) { return; }
20146#endif
20147
20148 is_valid_ = true;
20149 }
20150}
20151
20152inline WebSocketClient::~WebSocketClient() { shutdown_and_close(); }
20153
20154inline bool WebSocketClient::is_valid() const { return is_valid_; }
20155
20156inline void WebSocketClient::shutdown_and_close() {
20157#ifdef CPPHTTPLIB_SSL_ENABLED
20158 if (is_ssl_) {
20159 if (tls_session_) {
20160 tls::shutdown(tls_session_, true);
20161 tls::free_session(tls_session_);
20162 tls_session_ = nullptr;
20163 }
20164 if (tls_ctx_) {
20165 tls::free_context(tls_ctx_);
20166 tls_ctx_ = nullptr;
20167 }
20168 }
20169#endif
20170 if (ws_ && ws_->is_open()) { ws_->close(); }
20171 ws_.reset();
20172 if (sock_ != INVALID_SOCKET) {
20174 detail::close_socket(sock_);
20175 sock_ = INVALID_SOCKET;
20176 }
20177}
20178
20179inline bool WebSocketClient::create_stream(std::unique_ptr<Stream> &strm) {
20180#ifdef CPPHTTPLIB_SSL_ENABLED
20181 if (is_ssl_) {
20182 if (!detail::setup_client_tls_session(
20183 host_, tls_ctx_, tls_session_, sock_,
20184 server_certificate_verification_, ca_cert_file_path_,
20185 ca_cert_store_, read_timeout_sec_, read_timeout_usec_)) {
20186 return false;
20187 }
20188
20189 strm = std::unique_ptr<Stream>(new detail::SSLSocketStream(
20190 sock_, tls_session_, read_timeout_sec_, read_timeout_usec_,
20191 write_timeout_sec_, write_timeout_usec_));
20192 return true;
20193 }
20194#endif
20195 strm = std::unique_ptr<Stream>(
20196 new detail::SocketStream(sock_, read_timeout_sec_, read_timeout_usec_,
20197 write_timeout_sec_, write_timeout_usec_));
20198 return true;
20199}
20200
20202 if (!is_valid_) { return false; }
20203 shutdown_and_close();
20204
20205 Error error;
20207 host_, std::string(), port_, address_family_, tcp_nodelay_, ipv6_v6only_,
20208 socket_options_, connection_timeout_sec_, connection_timeout_usec_,
20209 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
20210 write_timeout_usec_, interface_, error);
20211
20212 if (sock_ == INVALID_SOCKET) { return false; }
20213
20214 std::unique_ptr<Stream> strm;
20215 if (!create_stream(strm)) {
20216 shutdown_and_close();
20217 return false;
20218 }
20219
20220 std::string selected_subprotocol;
20221 if (!detail::perform_websocket_handshake(*strm, host_, port_, path_, headers_,
20222 selected_subprotocol)) {
20223 shutdown_and_close();
20224 return false;
20225 }
20226 subprotocol_ = std::move(selected_subprotocol);
20227
20228 Request req;
20229 req.method = "GET";
20230 req.path = path_;
20231 ws_ = std::unique_ptr<WebSocket>(
20232 new WebSocket(std::move(strm), req, false, websocket_ping_interval_sec_));
20233 return true;
20234}
20235
20236inline ReadResult WebSocketClient::read(std::string &msg) {
20237 if (!ws_) { return Fail; }
20238 return ws_->read(msg);
20239}
20240
20241inline bool WebSocketClient::send(const std::string &data) {
20242 if (!ws_) { return false; }
20243 return ws_->send(data);
20244}
20245
20246inline bool WebSocketClient::send(const char *data, size_t len) {
20247 if (!ws_) { return false; }
20248 return ws_->send(data, len);
20249}
20250
20252 const std::string &reason) {
20253 if (ws_) { ws_->close(status, reason); }
20254}
20255
20256inline bool WebSocketClient::is_open() const { return ws_ && ws_->is_open(); }
20257
20258inline const std::string &WebSocketClient::subprotocol() const {
20259 return subprotocol_;
20260}
20261
20262inline void WebSocketClient::set_read_timeout(time_t sec, time_t usec) {
20263 read_timeout_sec_ = sec;
20264 read_timeout_usec_ = usec;
20265}
20266
20267inline void WebSocketClient::set_write_timeout(time_t sec, time_t usec) {
20268 write_timeout_sec_ = sec;
20269 write_timeout_usec_ = usec;
20270}
20271
20273 websocket_ping_interval_sec_ = sec;
20274}
20275
20276inline void WebSocketClient::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
20277
20279 address_family_ = family;
20280}
20281
20282inline void WebSocketClient::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
20283
20285 socket_options_ = std::move(socket_options);
20286}
20287
20288inline void WebSocketClient::set_connection_timeout(time_t sec, time_t usec) {
20289 connection_timeout_sec_ = sec;
20290 connection_timeout_usec_ = usec;
20291}
20292
20293inline void WebSocketClient::set_interface(const std::string &intf) {
20294 interface_ = intf;
20295}
20296
20297#ifdef CPPHTTPLIB_SSL_ENABLED
20298
20299inline void WebSocketClient::set_ca_cert_path(const std::string &path) {
20300 ca_cert_file_path_ = path;
20301}
20302
20303inline void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) {
20304 ca_cert_store_ = store;
20305}
20306
20307inline void
20308WebSocketClient::enable_server_certificate_verification(bool enabled) {
20309 server_certificate_verification_ = enabled;
20310}
20311
20312#endif // CPPHTTPLIB_SSL_ENABLED
20313
20314} // namespace ws
20315
20316// ----------------------------------------------------------------------------
20317
20318} // namespace httplib
20319
20320#endif // CPPHTTPLIB_HTTPLIB_H
Definition httplib.h:1971
void set_proxy_basic_auth(const std::string &username, const std::string &password)
Definition httplib.h:14575
void set_socket_options(SocketOptions socket_options)
Definition httplib.h:14553
time_t write_timeout_sec_
Definition httplib.h:2264
std::string host() const
Definition httplib.h:14485
virtual bool ensure_socket_connection(Socket &socket, Error &error)
Definition httplib.h:12281
bool decompress_
Definition httplib.h:2283
void set_basic_auth(const std::string &username, const std::string &password)
Definition httplib.h:14515
time_t read_timeout_sec_
Definition httplib.h:2262
void set_error_logger(ErrorLogger error_logger)
Definition httplib.h:14649
size_t socket_requests_in_flight_
Definition httplib.h:2242
void close_socket(Socket &socket)
Definition httplib.h:12305
std::string proxy_bearer_token_auth_token_
Definition httplib.h:2295
bool write_content_with_provider(Stream &strm, const Request &req, Error &error) const
Definition httplib.h:13094
void set_decompress(bool on)
Definition httplib.h:14559
socket_t socket() const
Definition httplib.h:14494
void set_proxy(const std::string &host, int port)
Definition httplib.h:14570
Socket socket_
Definition httplib.h:2237
void set_interface(const std::string &intf)
Definition httplib.h:14566
time_t connection_timeout_sec_
Definition httplib.h:2260
bool follow_location_
Definition httplib.h:2273
Result Patch(const std::string &path)
Definition httplib.h:14178
void stop()
Definition httplib.h:14462
bool tcp_nodelay_
Definition httplib.h:2278
const std::string host_
Definition httplib.h:2233
time_t max_timeout_msec_
Definition httplib.h:2266
time_t read_timeout_usec_
Definition httplib.h:2263
std::string proxy_host_
Definition httplib.h:2290
Result Delete(const std::string &path, DownloadProgress progress=nullptr)
Definition httplib.h:14377
void set_tcp_nodelay(bool on)
Definition httplib.h:14549
void set_proxy_bearer_token_auth(const std::string &token)
Definition httplib.h:14581
std::string basic_auth_password_
Definition httplib.h:2269
bool send(Request &req, Response &res, Error &error)
Definition httplib.h:12362
size_t payload_max_length_
Definition httplib.h:2285
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:12285
virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully)
Definition httplib.h:12292
void set_connection_timeout(time_t sec, time_t usec=0)
Definition httplib.h:14496
std::string bearer_token_auth_token_
Definition httplib.h:2270
void set_hostname_addr_map(std::map< std::string, std::string > addr_map)
Definition httplib.h:14532
int proxy_port_
Definition httplib.h:2291
void set_logger(Logger logger)
Definition httplib.h:14645
ClientImpl(const std::string &host)
Definition httplib.h:12178
void set_compress(bool on)
Definition httplib.h:14557
void output_log(const Request &req, const Response &res) const
Definition httplib.h:13444
bool path_encode_
Definition httplib.h:2275
int port() const
Definition httplib.h:14487
int address_family_
Definition httplib.h:2277
void set_address_family(int family)
Definition httplib.h:14545
std::string client_key_path_
Definition httplib.h:2258
std::string interface_
Definition httplib.h:2288
Result Put(const std::string &path)
Definition httplib.h:13981
Headers default_headers_
Definition httplib.h:2250
std::string client_cert_path_
Definition httplib.h:2257
void set_ipv6_v6only(bool on)
Definition httplib.h:14551
bool ipv6_v6only_
Definition httplib.h:2279
Result Post(const std::string &path)
Definition httplib.h:13783
time_t connection_timeout_usec_
Definition httplib.h:2261
std::string proxy_basic_auth_username_
Definition httplib.h:2293
bool has_payload_max_length_
Definition httplib.h:2286
void shutdown_socket(Socket &socket) const
Definition httplib.h:12300
Logger logger_
Definition httplib.h:2298
std::function< ssize_t(Stream &, Headers &)> header_writer_
Definition httplib.h:2253
bool process_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error)
Definition httplib.h:13460
std::thread::id socket_requests_are_from_thread_
Definition httplib.h:2243
bool compress_
Definition httplib.h:2282
std::mutex logger_mutex_
Definition httplib.h:2297
std::map< std::string, std::string > addr_map_
Definition httplib.h:2247
ErrorLogger error_logger_
Definition httplib.h:2299
time_t write_timeout_usec_
Definition httplib.h:2265
std::string proxy_basic_auth_password_
Definition httplib.h:2294
void set_write_timeout(time_t sec, time_t usec=0)
Definition httplib.h:14506
void copy_settings(const ClientImpl &rhs)
Definition httplib.h:12208
void output_error_log(const Error &err, const Request *req) const
Definition httplib.h:13452
void set_max_timeout(time_t msec)
Definition httplib.h:14511
void set_header_writer(std::function< ssize_t(Stream &, Headers &)> const &writer)
Definition httplib.h:14540
std::recursive_mutex request_mutex_
Definition httplib.h:2239
const int port_
Definition httplib.h:2234
Result Options(const std::string &path)
Definition httplib.h:14445
size_t is_socket_open() const
Definition httplib.h:14489
void set_default_headers(Headers headers)
Definition httplib.h:14536
void set_payload_max_length(size_t length)
Definition httplib.h:14561
void set_follow_location(bool on)
Definition httplib.h:14527
SocketOptions socket_options_
Definition httplib.h:2280
void set_keep_alive(bool on)
Definition httplib.h:14525
void set_path_encode(bool on)
Definition httplib.h:14529
bool socket_should_be_closed_when_request_is_done_
Definition httplib.h:2244
bool keep_alive_
Definition httplib.h:2272
Result Head(const std::string &path)
Definition httplib.h:13766
std::mutex socket_mutex_
Definition httplib.h:2238
virtual ~ClientImpl()
Definition httplib.h:12190
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:12544
std::string basic_auth_username_
Definition httplib.h:2268
virtual bool create_and_connect_socket(Socket &socket, Error &error)
Definition httplib.h:12273
Result Get(const std::string &path, DownloadProgress progress=nullptr)
Definition httplib.h:13671
virtual bool is_valid() const
Definition httplib.h:12206
void set_read_timeout(time_t sec, time_t usec=0)
Definition httplib.h:14501
void set_bearer_token_auth(const std::string &token)
Definition httplib.h:14521
Definition httplib.h:2388
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:15228
void set_read_timeout(time_t sec, time_t usec=0)
Definition httplib.h:15278
Result Post(const std::string &path)
Definition httplib.h:14797
Client(Client &&)=default
Result Put(const std::string &path)
Definition httplib.h:14924
void set_connection_timeout(time_t sec, time_t usec=0)
Definition httplib.h:15274
bool send(Request &req, Response &res, Error &error)
Definition httplib.h:15234
Result Options(const std::string &path)
Definition httplib.h:15220
void set_default_headers(Headers headers)
Definition httplib.h:15255
void set_proxy_bearer_token_auth(const std::string &token)
Definition httplib.h:15326
void set_hostname_addr_map(std::map< std::string, std::string > addr_map)
Definition httplib.h:15251
Client & operator=(Client &&)=default
void set_proxy_basic_auth(const std::string &username, const std::string &password)
Definition httplib.h:15322
void set_tcp_nodelay(bool on)
Definition httplib.h:15268
void set_keep_alive(bool on)
Definition httplib.h:15294
void stop()
Definition httplib.h:15240
void set_max_timeout(time_t msec)
Definition httplib.h:2919
void set_interface(const std::string &intf)
Definition httplib.h:15315
socket_t socket() const
Definition httplib.h:15248
Result Get(const std::string &path, DownloadProgress progress=nullptr)
Definition httplib.h:14740
void set_decompress(bool on)
Definition httplib.h:15309
size_t is_socket_open() const
Definition httplib.h:15246
bool is_valid() const
Definition httplib.h:14736
void set_follow_location(bool on)
Definition httplib.h:15295
void set_path_encode(bool on)
Definition httplib.h:15299
void set_proxy(const std::string &host, int port)
Definition httplib.h:15319
void set_write_timeout(time_t sec, time_t usec=0)
Definition httplib.h:15282
Client(const std::string &scheme_host_port)
Definition httplib.h:14673
void set_address_family(int family)
Definition httplib.h:15264
Result Head(const std::string &path)
Definition httplib.h:14792
std::string host() const
Definition httplib.h:15242
void set_error_logger(ErrorLogger error_logger)
Definition httplib.h:15334
void set_bearer_token_auth(const std::string &token)
Definition httplib.h:15290
void set_payload_max_length(size_t length)
Definition httplib.h:15311
void set_url_encode(bool on)
Definition httplib.h:15303
void set_logger(Logger logger)
Definition httplib.h:15330
void set_header_writer(std::function< ssize_t(Stream &, Headers &)> const &writer)
Definition httplib.h:15259
Result Patch(const std::string &path)
Definition httplib.h:15050
void set_socket_options(SocketOptions socket_options)
Definition httplib.h:15270
void set_compress(bool on)
Definition httplib.h:15307
Result Delete(const std::string &path, DownloadProgress progress=nullptr)
Definition httplib.h:15179
void set_basic_auth(const std::string &username, const std::string &password)
Definition httplib.h:15286
int port() const
Definition httplib.h:15244
Definition httplib.h:1100
Reader reader_
Definition httplib.h:1118
ContentReader(Reader reader, FormDataReader multipart_reader)
Definition httplib.h:1106
bool operator()(FormDataHeader header, ContentReceiver receiver) const
Definition httplib.h:1110
bool operator()(ContentReceiver receiver) const
Definition httplib.h:1114
std::function< bool(ContentReceiver receiver)> Reader
Definition httplib.h:1102
FormDataReader formdata_reader_
Definition httplib.h:1119
std::function< bool(FormDataHeader header, ContentReceiver receiver)> FormDataReader
Definition httplib.h:1103
Definition httplib.h:986
std::function< bool(const char *data, size_t data_len)> write
Definition httplib.h:995
DataSink(DataSink &&)=delete
DataSink(const DataSink &)=delete
DataSink & operator=(DataSink &&)=delete
DataSink & operator=(const DataSink &)=delete
std::function< void()> done
Definition httplib.h:997
std::function< bool()> is_writable
Definition httplib.h:996
DataSink()
Definition httplib.h:988
std::ostream os
Definition httplib.h:999
std::function< void(const Headers &trailer)> done_with_trailer
Definition httplib.h:998
Definition httplib.h:1835
bool has_request_header(const std::string &key) const
Definition httplib.h:9871
Response & value()
Definition httplib.h:1847
Result()=default
const Response & value() const
Definition httplib.h:1846
size_t get_request_header_value_count(const std::string &key) const
Definition httplib.h:9882
const Response & operator*() const
Definition httplib.h:1848
bool operator==(std::nullptr_t) const
Definition httplib.h:1844
Result(std::unique_ptr< Response > &&res, Error err, Headers &&request_headers=Headers{})
Definition httplib.h:1838
bool operator!=(std::nullptr_t) const
Definition httplib.h:1845
const Response * operator->() const
Definition httplib.h:1850
Response * operator->()
Definition httplib.h:1851
size_t get_request_header_value_u64(const std::string &key, size_t def=0, size_t id=0) const
Definition httplib.h:9865
Error error() const
Definition httplib.h:1854
Response & operator*()
Definition httplib.h:1849
std::string get_request_header_value(const std::string &key, const char *def="", size_t id=0) const
Definition httplib.h:9875
Definition httplib.h:1573
time_t read_timeout_usec_
Definition httplib.h:1709
Server & set_default_file_mimetype(const std::string &mime)
Definition httplib.h:10741
void stop()
Definition httplib.h:10918
std::function< int(const Request &, Response &)> Expect100ContinueHandler
Definition httplib.h:1590
Server & Put(const std::string &pattern, Handler handler)
Definition httplib.h:10639
Server & Delete(const std::string &pattern, Handler handler)
Definition httplib.h:10663
Server & set_pre_routing_handler(HandlerWithResponse handler)
Definition httplib.h:10771
Server & set_write_timeout(time_t sec, time_t usec=0)
Definition httplib.h:10860
time_t idle_interval_usec_
Definition httplib.h:1713
size_t keep_alive_max_count_
Definition httplib.h:1706
time_t write_timeout_sec_
Definition httplib.h:1710
Server & set_error_handler(ErrorHandlerFunc &&handler)
Definition httplib.h:1630
bool listen(const std::string &host, int port, int socket_flags=0)
Definition httplib.h:10905
Server & WebSocket(const std::string &pattern, WebSocketHandler handler)
Definition httplib.h:10680
bool set_base_dir(const std::string &dir, const std::string &mount_point=std::string())
Definition httplib.h:10695
void decommission()
Definition httplib.h:10928
bool listen_after_bind()
Definition httplib.h:10903
std::vector< std::string > trusted_proxies_
Definition httplib.h:1704
Server & set_tcp_nodelay(bool on)
Definition httplib.h:10812
Server & set_header_writer(std::function< ssize_t(Stream &, Headers &)> const &writer)
Definition httplib.h:10832
Server & set_payload_max_length(size_t length)
Definition httplib.h:10872
Server & Options(const std::string &pattern, Handler handler)
Definition httplib.h:10675
virtual bool is_valid() const
Definition httplib.h:12124
int bind_to_any_port(const std::string &host, int socket_flags=0)
Definition httplib.h:10897
Server()
Definition httplib.h:10601
time_t idle_interval_sec_
Definition httplib.h:1712
bool bind_to_port(const std::string &host, int port, int socket_flags=0)
Definition httplib.h:10891
time_t write_timeout_usec_
Definition httplib.h:1711
time_t keep_alive_timeout_sec_
Definition httplib.h:1707
HandlerResponse
Definition httplib.h:1580
@ Unhandled
Definition httplib.h:1582
@ Handled
Definition httplib.h:1581
Server & Patch(const std::string &pattern, Handler handler)
Definition httplib.h:10651
Server & set_default_headers(Headers headers)
Definition httplib.h:10827
time_t websocket_ping_interval_sec_
Definition httplib.h:1715
Server & Get(const std::string &pattern, Handler handler)
Definition httplib.h:10622
Server & set_trusted_proxies(const std::vector< std::string > &proxies)
Definition httplib.h:10839
Server & set_file_request_handler(Handler handler)
Definition httplib.h:10746
std::function< void(const Request &, Response &)> Handler
Definition httplib.h:1575
std::function< std::string(const std::vector< std::string > &protocols)> SubProtocolSelector
Definition httplib.h:1595
std::function< void(const Request &, Response &, std::exception_ptr ep)> ExceptionHandler
Definition httplib.h:1577
std::atomic< socket_t > svr_sock_
Definition httplib.h:1702
void wait_until_ready() const
Definition httplib.h:10912
Server & set_post_routing_handler(Handler handler)
Definition httplib.h:10776
bool is_running() const
Definition httplib.h:10910
Server & Post(const std::string &pattern, Handler handler)
Definition httplib.h:10627
Server & set_idle_interval(time_t sec, time_t usec=0)
Definition httplib.h:10866
Server & set_address_family(int family)
Definition httplib.h:10807
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:11871
bool remove_mount_point(const std::string &mount_point)
Definition httplib.h:10724
Server & set_keep_alive_timeout(time_t sec)
Definition httplib.h:10849
std::function< TaskQueue *(void)> new_task_queue
Definition httplib.h:1692
Server & set_logger(Logger logger)
Definition httplib.h:10786
std::function< HandlerResponse(const Request &, Response &)> HandlerWithResponse
Definition httplib.h:1584
std::function< void(const Request &, ws::WebSocket &)> WebSocketHandler
Definition httplib.h:1593
bool set_mount_point(const std::string &mount_point, const std::string &dir, Headers headers=Headers())
Definition httplib.h:10700
Server & set_socket_options(SocketOptions socket_options)
Definition httplib.h:10822
Server & set_pre_compression_logger(Logger logger)
Definition httplib.h:10796
time_t read_timeout_sec_
Definition httplib.h:1708
Server & set_pre_request_handler(HandlerWithResponse handler)
Definition httplib.h:10781
Server & set_exception_handler(ExceptionHandler handler)
Definition httplib.h:10766
Server & set_error_logger(ErrorLogger error_logger)
Definition httplib.h:10791
Server & set_expect_100_continue_handler(Expect100ContinueHandler handler)
Definition httplib.h:10802
Server & set_websocket_ping_interval(time_t sec)
Definition httplib.h:10877
Server & set_ipv6_v6only(bool on)
Definition httplib.h:10817
size_t payload_max_length_
Definition httplib.h:1714
virtual ~Server()
Server & set_file_extension_and_mimetype_mapping(const std::string &ext, const std::string &mime)
Definition httplib.h:10735
std::function< void( const Request &, Response &, const ContentReader &content_reader)> HandlerWithContentReader
Definition httplib.h:1587
Server & set_keep_alive_max_count(size_t count)
Definition httplib.h:10844
Server & set_read_timeout(time_t sec, time_t usec=0)
Definition httplib.h:10854
Definition httplib.h:1399
Error get_error() const
Definition httplib.h:1424
virtual bool wait_writable() const =0
Error error_
Definition httplib.h:1427
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:1406
virtual time_t duration() const =0
virtual void set_read_timeout(time_t sec, time_t usec=0)
Definition httplib.h:1416
virtual ~Stream()=default
virtual socket_t socket() const =0
virtual bool is_readable() const =0
Definition httplib.h:1430
virtual bool enqueue(std::function< void()> fn)=0
virtual void on_idle()
Definition httplib.h:1438
virtual ~TaskQueue()=default
virtual void shutdown()=0
Definition httplib.h:1441
ThreadPool(size_t n, size_t max_n=0, size_t mqr=0)
Definition httplib.h:9971
void shutdown() override
Definition httplib.h:10008
ThreadPool(const ThreadPool &)=delete
~ThreadPool() override=default
bool enqueue(std::function< void()> fn) override
Definition httplib.h:9987
Definition httplib.h:858
bool has_value() const noexcept
Definition httplib.h:884
void reset() noexcept
Definition httplib.h:885
any & operator=(any &&) noexcept=default
any & operator=(T &&v)
Definition httplib.h:879
any() noexcept=default
any(any &&) noexcept=default
Definition httplib.h:824
const char * what() const noexcept override
Definition httplib.h:826
bool wait_writable() const override
Definition httplib.h:10264
void get_remote_ip_and_port(std::string &ip, int &port) const override
Definition httplib.h:10281
void get_local_ip_and_port(std::string &ip, int &port) const override
Definition httplib.h:10284
ssize_t write(const char *ptr, size_t size) override
Definition httplib.h:10276
~BufferStream() override=default
time_t duration() const override
Definition httplib.h:10289
bool wait_readable() const override
Definition httplib.h:10262
const std::string & get_buffer() const
Definition httplib.h:10291
socket_t socket() const override
Definition httplib.h:10287
bool is_readable() const override
Definition httplib.h:10260
ssize_t read(char *ptr, size_t size) override
Definition httplib.h:10266
bool operator()(size_t offset, size_t, DataSink &sink)
Definition httplib.h:8577
ContentProviderAdapter(ContentProviderWithoutLength &&content_provider)
Definition httplib.h:8573
void set_boundary(std::string &&boundary)
Definition httplib.h:7842
bool parse(const char *buf, size_t n, const FormDataHeader &header_callback, const ContentReceiver &content_callback)
Definition httplib.h:7850
bool is_valid() const
Definition httplib.h:7848
virtual bool match(Request &request) const =0
virtual ~MatcherBase()=default
MatcherBase(std::string pattern)
Definition httplib.h:1494
const std::string & pattern() const
Definition httplib.h:1497
PathParamsMatcher(const std::string &pattern)
Definition httplib.h:10293
bool match(Request &request) const override
Definition httplib.h:10342
bool match(Request &request) const override
Definition httplib.h:10385
RegexMatcher(const std::string &pattern)
Definition httplib.h:1555
Definition httplib.h:5551
bool wait_readable() const override
Definition httplib.h:10142
bool wait_writable() const override
Definition httplib.h:10155
void get_remote_ip_and_port(std::string &ip, int &port) const override
Definition httplib.h:10236
socket_t socket() const override
Definition httplib.h:10246
bool is_peer_alive() const override
Definition httplib.h:10159
time_t duration() const override
Definition httplib.h:10248
bool is_readable() const override
Definition httplib.h:10138
ssize_t write(const char *ptr, size_t size) override
Definition httplib.h:10225
void get_local_ip_and_port(std::string &ip, int &port) const override
Definition httplib.h:10241
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:10124
ssize_t read(char *ptr, size_t size) override
Definition httplib.h:10163
void set_read_timeout(time_t sec, time_t usec=0) override
Definition httplib.h:10254
Definition httplib.h:3088
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:3092
virtual ~compressor()=default
Definition httplib.h:3097
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:3103
const char * data() const
Definition httplib.h:5405
bool open(const char *path)
Definition httplib.h:5330
~mmap()
Definition httplib.h:5328
size_t size() const
Definition httplib.h:5403
mmap(const char *path)
Definition httplib.h:5326
bool is_open() const
Definition httplib.h:5399
void close()
Definition httplib.h:5409
Definition httplib.h:3108
~nocompressor() override=default
bool compress(const char *data, size_t data_length, bool, Callback callback) override
Definition httplib.h:6644
Definition httplib.h:3205
stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size)
Definition httplib.h:5247
bool getline()
Definition httplib.h:5273
bool end_with_crlf() const
Definition httplib.h:5268
size_t size() const
Definition httplib.h:5260
const char * ptr() const
Definition httplib.h:5252
void start_async()
Definition httplib.h:4032
const std::string & last_event_id() const
Definition httplib.h:4023
bool is_connected() const
Definition httplib.h:4021
SSEClient & on_event(const std::string &type, MessageHandler handler)
Definition httplib.h:3989
SSEClient & set_reconnect_interval(int ms)
Definition httplib.h:4005
SSEClient & on_message(MessageHandler handler)
Definition httplib.h:3984
SSEClient & set_max_reconnect_attempts(int n)
Definition httplib.h:4010
SSEClient(const SSEClient &)=delete
SSEClient & on_open(OpenHandler handler)
Definition httplib.h:3995
SSEClient & operator=(const SSEClient &)=delete
~SSEClient()
Definition httplib.h:3982
SSEClient & set_headers(const Headers &headers)
Definition httplib.h:4015
void stop()
Definition httplib.h:4037
std::function< void(Error)> ErrorHandler
Definition httplib.h:3648
SSEClient & on_error(ErrorHandler handler)
Definition httplib.h:4000
SSEClient(Client &client, const std::string &path)
Definition httplib.h:3975
std::function< void(const SSEMessage &)> MessageHandler
Definition httplib.h:3647
std::function< void()> OpenHandler
Definition httplib.h:3649
void start()
Definition httplib.h:4027
Definition httplib.h:3351
Result & operator=(const Result &)=delete
Result & operator=(Result &&other) noexcept
Definition httplib.h:3896
const char * data() const
Definition httplib.h:3950
Result()
Definition httplib.h:3883
size_t size() const
Definition httplib.h:3951
Error error() const
Definition httplib.h:3930
bool has_read_error() const
Definition httplib.h:3932
bool is_valid() const
Definition httplib.h:3909
const Headers & headers() const
Definition httplib.h:3916
int status() const
Definition httplib.h:3912
std::string get_header_value(const std::string &key, const char *def="") const
Definition httplib.h:3921
bool has_header(const std::string &key) const
Definition httplib.h:3926
std::string read_all()
Definition httplib.h:3953
bool next()
Definition httplib.h:3934
Error read_error() const
Definition httplib.h:3931
Result(const Result &)=delete
bool send(const std::string &data)
Definition httplib.h:20241
WebSocketClient(const std::string &scheme_host_port_path, const Headers &headers={})
Definition httplib.h:20109
bool connect()
Definition httplib.h:20201
WebSocketClient & operator=(const WebSocketClient &)=delete
ReadResult read(std::string &msg)
Definition httplib.h:20236
void set_address_family(int family)
Definition httplib.h:20278
~WebSocketClient()
Definition httplib.h:20152
void set_read_timeout(time_t sec, time_t usec=0)
Definition httplib.h:20262
void close(CloseStatus status=CloseStatus::Normal, const std::string &reason="")
Definition httplib.h:20251
void set_ipv6_v6only(bool on)
Definition httplib.h:20282
WebSocketClient(const WebSocketClient &)=delete
void set_connection_timeout(time_t sec, time_t usec=0)
Definition httplib.h:20288
bool is_valid() const
Definition httplib.h:20154
const std::string & subprotocol() const
Definition httplib.h:20258
void set_socket_options(SocketOptions socket_options)
Definition httplib.h:20284
void set_websocket_ping_interval(time_t sec)
Definition httplib.h:20272
void set_write_timeout(time_t sec, time_t usec=0)
Definition httplib.h:20267
void set_tcp_nodelay(bool on)
Definition httplib.h:20276
bool is_open() const
Definition httplib.h:20256
void set_interface(const std::string &intf)
Definition httplib.h:20293
Definition httplib.h:3743
bool send(const std::string &data)
Definition httplib.h:20042
const Request & request() const
Definition httplib.h:20104
void close(CloseStatus status=CloseStatus::Normal, const std::string &reason="")
Definition httplib.h:20050
bool is_open() const
Definition httplib.h:20106
ReadResult read(std::string &msg)
Definition httplib.h:19953
friend class WebSocketClient
Definition httplib.h:3759
WebSocket(const WebSocket &)=delete
~WebSocket()
Definition httplib.h:20078
WebSocket & operator=(const WebSocket &)=delete
#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:296
#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:294
#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_CONNECTION_TIMEOUT_USECOND
Definition httplib.h:42
#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND
Definition httplib.h:94
Definition conf.py:1
Definition value.hpp:34
Definition httplib.h:532
std::unordered_set< T, detail::case_ignore::hash, detail::case_ignore::equal_to > unordered_set
Definition httplib.h:596
bool equal(const std::string &a, const std::string &b)
Definition httplib.h:566
unsigned char to_lower(int c)
Definition httplib.h:534
Definition httplib.h:3268
bool is_obs_text(char c)
Definition httplib.h:8606
bool is_vchar(char c)
Definition httplib.h:8604
bool is_field_value(const std::string &s)
Definition httplib.h:8635
bool is_token(const std::string &s)
Definition httplib.h:8594
bool is_token_char(char c)
Definition httplib.h:8588
bool is_field_name(const std::string &s)
Definition httplib.h:8602
bool is_field_content(const std::string &s)
Definition httplib.h:8610
bool is_field_vchar(char c)
Definition httplib.h:8608
Definition httplib.h:6400
Definition httplib.h:509
size_t to_utf8(int code, char *buff)
Definition httplib.h:4483
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:5164
std::string make_host_and_port_string_always_port(const std::string &host, int port)
Definition httplib.h:10419
std::string trim_double_quotes_copy(const std::string &s)
Definition httplib.h:5156
ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags)
Definition httplib.h:5473
std::string sha1(const std::string &input)
Definition httplib.h:4595
std::string make_host_and_port_string(const std::string &host, int port, bool is_ssl)
Definition httplib.h:10403
void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port)
Definition httplib.h:6356
bool parse_www_authenticate(const Response &res, std::map< std::string, std::string > &auth, bool is_proxy)
Definition httplib.h:8537
bool parse_quality(const char *b, const char *e, std::string &token, double &quality)
Definition httplib.h:6534
std::string serialize_multipart_formdata_get_content_type(const std::string &boundary)
Definition httplib.h:8184
bool is_socket_alive(socket_t sock)
Definition httplib.h:5540
void coalesce_ranges(Ranges &ranges, size_t content_length)
Definition httplib.h:8270
bool has_header(const Headers &headers, const std::string &key)
Definition httplib.h:6952
size_t get_multipart_content_length(const UploadFormDataItems &items, const std::string &boundary)
Definition httplib.h:8203
int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout)
Definition httplib.h:5486
EncodingType
Definition httplib.h:3062
@ Zstd
Definition httplib.h:3062
@ Gzip
Definition httplib.h:3062
@ Brotli
Definition httplib.h:3062
@ None
Definition httplib.h:3062
std::string compute_etag(const FileStat &fs)
Definition httplib.h:4397
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:2821
time_t parse_http_date(const std::string &date_str)
Definition httplib.h:4435
void split(const char *b, const char *e, char d, std::function< void(const char *, const char *)> fn)
Definition httplib.h:5184
bool write_websocket_frame(Stream &strm, ws::Opcode opcode, const char *data, size_t len, bool fin, bool mask)
Definition httplib.h:4724
std::string normalize_query_string(const std::string &query)
Definition httplib.h:7633
const void * any_type_id
Definition httplib.h:831
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:7078
bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, DownloadProgress progress, ContentReceiverWithProgress receiver, bool decompress)
Definition httplib.h:7237
std::pair< std::unique_ptr< compressor >, const char * > create_compressor()
Definition httplib.h:6928
std::unique_ptr< decompressor > create_decompressor(const std::string &encoding)
Definition httplib.h:6905
std::string trim_copy(const std::string &s)
Definition httplib.h:5151
std::string base64_encode(const std::string &in)
Definition httplib.h:4567
std::string file_mtime_to_http_date(time_t mtime)
Definition httplib.h:4417
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:8639
std::string serialize_multipart_formdata_item_end()
Definition httplib.h:8176
bool is_hex(char c, int &v)
Definition httplib.h:4356
bool write_content_without_length(Stream &strm, const ContentProvider &content_provider, const T &is_shutting_down)
Definition httplib.h:7417
std::string if2ip(int address_family, const std::string &ifn)
Definition httplib.h:6228
std::string extract_media_type(const std::string &content_type, std::map< std::string, std::string > *params=nullptr)
Definition httplib.h:6475
bool is_space_or_tab(char c)
Definition httplib.h:4995
ssize_t write_response_line(Stream &strm, int status)
Definition httplib.h:7305
ssize_t read_body_content(Stream *stream, BodyReader &br, char *buf, size_t len)
Definition httplib.h:1961
bool redirect(T &cli, Request &req, Response &res, const std::string &path, const std::string &location, Error &error)
Definition httplib.h:7564
bool parse_trailers(stream_line_reader &line_reader, Headers &dest, const Headers &src_headers)
Definition httplib.h:5047
std::enable_if<!std::is_array< T >::value, std::unique_ptr< T > >::type make_unique(Args &&...args)
Definition httplib.h:521
bool is_weak_etag(const std::string &s)
Definition httplib.h:4472
ssize_t select_impl(socket_t sock, short events, time_t sec, time_t usec)
Definition httplib.h:5494
bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, int &val)
Definition httplib.h:4370
void parse_query_text(const char *data, std::size_t size, Params &params)
Definition httplib.h:7602
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:5655
void set_nonblocking(socket_t sock, bool nonblocking)
Definition httplib.h:6177
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:6033
int shutdown_socket(socket_t sock)
Definition httplib.h:5667
bool keep_alive(const std::atomic< socket_t > &svr_sock, socket_t sock, time_t keep_alive_timeout_sec)
Definition httplib.h:5588
std::string serialize_multipart_formdata_item_begin(const T &item, const std::string &boundary)
Definition httplib.h:8160
bool set_socket_opt_impl(socket_t sock, int level, int optname, const void *optval, socklen_t optlen)
Definition httplib.h:4329
bool is_strong_etag(const std::string &s)
Definition httplib.h:4477
std::pair< size_t, size_t > get_range_offset_and_length(Range r, size_t content_length)
Definition httplib.h:8395
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:7344
std::string file_extension(const std::string &path)
Definition httplib.h:4988
void parse_disposition_params(const std::string &s, Params &params)
Definition httplib.h:7671
unsigned int str2tag(const std::string &s)
Definition httplib.h:6396
bool read_websocket_upgrade_response(Stream &strm, const std::string &expected_accept, std::string &selected_subprotocol)
Definition httplib.h:7032
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:5211
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:10108
bool expect_content(const Request &req)
Definition httplib.h:8506
ssize_t handle_EINTR(T fn)
Definition httplib.h:5448
ssize_t select_write(socket_t sock, time_t sec, time_t usec)
Definition httplib.h:5510
bool get_ip_and_port(const struct sockaddr_storage &addr, socklen_t addr_len, std::string &ip, int &port)
Definition httplib.h:6325
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:8456
constexpr size_t str_len(const char(&)[N])
Definition httplib.h:2811
bool is_valid_path(const std::string &path)
Definition httplib.h:4866
std::string websocket_accept_key(const std::string &client_key)
Definition httplib.h:4685
bool set_socket_opt(socket_t sock, int level, int optname, int optval)
Definition httplib.h:4340
bool parse_header(const char *beg, const char *end, T fn)
Definition httplib.h:5001
std::string random_string(size_t length)
Definition httplib.h:8121
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:8420
std::pair< size_t, size_t > trim(const char *b, const char *e, size_t left, size_t right)
Definition httplib.h:5140
bool range_error(Request &req, Response &res)
Definition httplib.h:8326
EncodingType encoding_type(const Request &req, const Response &res)
Definition httplib.h:6587
bool bind_ip_address(socket_t sock, const std::string &host)
Definition httplib.h:6196
bool parse_multipart_boundary(const std::string &content_type, std::string &boundary)
Definition httplib.h:7661
ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags)
Definition httplib.h:5461
bool parse_port(const char *s, size_t len, int &port)
Definition httplib.h:692
std::string serialize_multipart_formdata_finish(const std::string &boundary)
Definition httplib.h:8179
std::string params_to_query_str(const Params &params)
Definition httplib.h:7590
int close_socket(socket_t sock)
Definition httplib.h:5440
ReadContentResult read_content_without_length(Stream &strm, size_t payload_max_length, ContentReceiverWithProgress out)
Definition httplib.h:7120
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:7185
ssize_t write_headers(Stream &strm, const Headers &headers)
Definition httplib.h:7314
bool is_chunked_transfer_encoding(const Headers &headers)
Definition httplib.h:7179
bool can_compress_content_type(const std::string &content_type)
Definition httplib.h:6509
std::string from_i_to_hex(size_t n)
Definition httplib.h:4387
bool write_content(Stream &strm, const ContentProvider &content_provider, size_t offset, size_t length, T is_shutting_down, Error &error)
Definition httplib.h:7399
bool is_path_within_base(const std::string &resolved_path, const std::string &resolved_base)
Definition httplib.h:4921
bool parse_accept_header(const std::string &s, std::vector< std::string > &content_types)
Definition httplib.h:7756
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:6265
bool is_websocket_upgrade(const Request &req)
Definition httplib.h:4690
bool parse_range_header(const std::string &s, Ranges &ranges)
Definition httplib.h:7698
from_chars_result< T > from_chars(const char *first, const char *last, T &value, int base=10)
Definition httplib.h:637
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:8492
void duration_to_sec_and_usec(const T &duration, U callback)
Definition httplib.h:2803
const char * get_header_value(const Headers &headers, const std::string &key, const char *def, size_t id)
Definition httplib.h:6957
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:6409
any_type_id any_typeid() noexcept
Definition httplib.h:836
bool canonicalize_path(const char *path, std::string &resolved)
Definition httplib.h:4908
ssize_t select_read(socket_t sock, time_t sec, time_t usec)
Definition httplib.h:5506
std::string serialize_multipart_formdata(const UploadFormDataItems &items, const std::string &boundary, bool finish=true)
Definition httplib.h:8189
bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec, time_t usec)
Definition httplib.h:4344
std::string make_multipart_data_boundary()
Definition httplib.h:8142
void get_local_ip_and_port(socket_t sock, std::string &ip, int &port)
Definition httplib.h:6347
ReadContentResult read_content_chunked(Stream &strm, T &x, size_t payload_max_length, ContentReceiverWithProgress out)
Definition httplib.h:7145
bool is_multipart_boundary_chars_valid(const std::string &boundary)
Definition httplib.h:8146
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:8472
ContentProvider make_multipart_content_provider(const UploadFormDataItems &items, const std::string &boundary)
Definition httplib.h:8223
ssize_t write_request_line(Stream &strm, const std::string &method, const std::string &path)
Definition httplib.h:7296
std::string make_content_range_header_field(const std::pair< size_t, size_t > &offset_and_length, size_t content_length)
Definition httplib.h:8405
std::string escape_abstract_namespace_unix_domain(const std::string &s)
Definition httplib.h:5675
bool read_headers(Stream &strm, Headers &headers)
Definition httplib.h:6976
std::string unescape_abstract_namespace_unix_domain(const std::string &s)
Definition httplib.h:5685
bool is_connection_error()
Definition httplib.h:6188
bool write_data(Stream &strm, const char *d, size_t l)
Definition httplib.h:7333
bool check_and_write_headers(Stream &strm, Headers &headers, T header_writer, Error &error)
Definition httplib.h:10424
Error wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec)
Definition httplib.h:5514
bool write_content_chunked(Stream &strm, const ContentProvider &content_provider, const T &is_shutting_down, U &compressor, Error &error)
Definition httplib.h:7452
ReadContentResult
Definition httplib.h:7072
@ PayloadTooLarge
Definition httplib.h:7074
@ Success
Definition httplib.h:7073
@ Error
Definition httplib.h:7075
bool is_prohibited_header_name(const std::string &name)
Definition httplib.h:6940
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:5641
std::string prepare_host_string(const std::string &host)
Definition httplib.h:10391
bool is_numeric(const std::string &str)
Definition httplib.h:2815
int getaddrinfo_with_timeout(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res, time_t timeout_sec)
Definition httplib.h:5694
constexpr unsigned int str2tag_core(const char *s, size_t l, unsigned int h)
Definition httplib.h:6384
std::string encode_path(const std::string &s)
Definition httplib.h:4956
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:5623
Definition httplib.h:3634
Definition httplib.h:3349
Result Options(ClientType &cli, const std::string &path, size_t chunk_size=8192)
Definition httplib.h:3608
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:3414
Result Delete(ClientType &cli, const std::string &path, size_t chunk_size=8192)
Definition httplib.h:3519
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:3449
Result Head(ClientType &cli, const std::string &path, size_t chunk_size=8192)
Definition httplib.h:3582
Result Get(ClientType &cli, const std::string &path, size_t chunk_size=8192)
Definition httplib.h:3388
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:3484
Definition httplib.h:3863
bool is_valid_utf8(const std::string &s)
Definition httplib.h:4520
bool read_websocket_frame(Stream &strm, Opcode &opcode, std::string &payload, bool &fin, bool expect_masked, size_t max_len)
Definition httplib.h:4789
Definition server.hpp:29
CloseStatus
Definition httplib.h:3727
@ Abnormal
Definition httplib.h:3733
@ MessageTooBig
Definition httplib.h:3736
@ NoStatus
Definition httplib.h:3732
@ InvalidPayload
Definition httplib.h:3734
@ InternalError
Definition httplib.h:3738
@ Normal
Definition httplib.h:3728
@ UnsupportedData
Definition httplib.h:3731
@ PolicyViolation
Definition httplib.h:3735
@ MandatoryExtension
Definition httplib.h:3737
@ GoingAway
Definition httplib.h:3729
@ ProtocolError
Definition httplib.h:3730
ReadResult
Definition httplib.h:3741
@ Binary
Definition httplib.h:3741
@ Text
Definition httplib.h:3741
@ Fail
Definition httplib.h:3741
Opcode
Definition httplib.h:3718
@ Pong
Definition httplib.h:3724
@ Binary
Definition httplib.h:3721
@ Text
Definition httplib.h:3720
@ Ping
Definition httplib.h:3723
@ Close
Definition httplib.h:3722
@ Continuation
Definition httplib.h:3719
Definition server.hpp:29
std::function< bool(size_t offset, DataSink &sink)> ContentProviderWithoutLength
Definition httplib.h:1022
std::ostream & operator<<(std::ostream &os, const Error &obj)
Definition httplib.h:9276
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:1036
std::pair< std::string, std::string > make_range_header(const Ranges &ranges)
Definition httplib.h:9589
std::string encode_path_component(const std::string &component)
Definition httplib.h:9403
std::function< bool(const Response &response)> ResponseHandler
Definition httplib.h:942
std::function< bool( const char *data, size_t data_length, size_t offset, size_t total_length)> ContentReceiverWithProgress
Definition httplib.h:1092
std::function< void(socket_t sock)> SocketOptions
Definition httplib.h:1478
std::string get_client_ip(const std::string &x_forwarded_for, const std::vector< std::string > &trusted_proxies)
Definition httplib.h:11837
std::function< bool(const FormData &file)> FormDataHeader
Definition httplib.h:1098
std::string hosted_at(const std::string &hostname)
Definition httplib.h:9282
std::string decode_uri_component(const std::string &value)
Definition httplib.h:9363
std::function< void(const Request &, const Response &)> Logger
Definition httplib.h:1472
std::vector< UploadFormData > UploadFormDataItems
Definition httplib.h:984
Error
Definition httplib.h:1355
@ Canceled
Definition httplib.h:1363
@ ConnectionTimeout
Definition httplib.h:1370
@ Write
Definition httplib.h:1361
@ ExceedUriMaxLength
Definition httplib.h:1377
@ TooManyFormDataFiles
Definition httplib.h:1375
@ SSLServerVerification
Definition httplib.h:1366
@ ConnectionClosed
Definition httplib.h:1372
@ SSLServerHostnameVerification
Definition httplib.h:1367
@ ExceedMaxSocketDescriptorCount
Definition httplib.h:1378
@ InvalidHTTPMethod
Definition httplib.h:1380
@ OpenFile
Definition httplib.h:1384
@ ResourceExhaustion
Definition httplib.h:1374
@ Success
Definition httplib.h:1356
@ BindIPAddress
Definition httplib.h:1359
@ Read
Definition httplib.h:1360
@ InvalidRequestLine
Definition httplib.h:1379
@ Compression
Definition httplib.h:1369
@ GetSockName
Definition httplib.h:1386
@ Unknown
Definition httplib.h:1357
@ InvalidHeaders
Definition httplib.h:1382
@ SSLConnection
Definition httplib.h:1364
@ UnsupportedAddressFamily
Definition httplib.h:1387
@ ProxyConnection
Definition httplib.h:1371
@ InvalidRangeHeader
Definition httplib.h:1389
@ ExceedMaxPayloadSize
Definition httplib.h:1376
@ MultipartParsing
Definition httplib.h:1383
@ Connection
Definition httplib.h:1358
@ Timeout
Definition httplib.h:1373
@ Listen
Definition httplib.h:1385
@ HTTPParsing
Definition httplib.h:1388
@ ExceedRedirectCount
Definition httplib.h:1362
@ InvalidHTTPVersion
Definition httplib.h:1381
@ SSLLoadingCerts
Definition httplib.h:1365
@ UnsupportedMultipartBoundaryChars
Definition httplib.h:1368
@ SSLPeerCouldBeClosed_
Definition httplib.h:1392
std::string to_string(Error error)
Definition httplib.h:9231
std::pair< size_t, ContentProvider > make_file_body(const std::string &filepath)
Definition httplib.h:1064
T * any_cast(any *a) noexcept
Definition httplib.h:891
std::string encode_uri_component(const std::string &value)
Definition httplib.h:9320
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:9603
std::function< bool(const char *data, size_t data_length)> ContentReceiver
Definition httplib.h:1095
std::function< bool(size_t offset, size_t length, DataSink &sink)> ContentProvider
Definition httplib.h:1019
std::string sanitize_filename(const std::string &filename)
Definition httplib.h:9554
const char * status_message(int status)
Definition httplib.h:9155
std::multimap< std::string, std::string > Params
Definition httplib.h:794
std::smatch Match
Definition httplib.h:795
SSLVerifierResponse
Definition httplib.h:706
@ CertificateAccepted
Definition httplib.h:710
@ CertificateRejected
Definition httplib.h:712
@ NoDecisionMade
Definition httplib.h:708
std::string get_bearer_token_auth(const Request &req)
Definition httplib.h:9146
std::pair< std::string, std::string > make_bearer_token_authentication_header(const std::string &token, bool is_proxy=false)
Definition httplib.h:9611
std::function< bool(size_t current, size_t total)> DownloadProgress
Definition httplib.h:797
std::unordered_multimap< std::string, std::string, detail::case_ignore::hash, detail::case_ignore::equal_to > Headers
Definition httplib.h:790
std::function< void(bool success)> ContentProviderResourceReleaser
Definition httplib.h:1025
std::vector< Range > Ranges
Definition httplib.h:1123
std::string decode_path_component(const std::string &component)
Definition httplib.h:9438
std::string decode_uri(const std::string &value)
Definition httplib.h:9383
std::string encode_query_component(const std::string &component, bool space_as_plus=true)
Definition httplib.h:9475
std::vector< FormDataProvider > FormDataProviderItems
Definition httplib.h:1033
std::multimap< std::string, FormField > FormFields
Definition httplib.h:957
std::function< void(const Error &, const Request *)> ErrorLogger
Definition httplib.h:1476
std::string decode_query_component(const std::string &component, bool plus_as_space=true)
Definition httplib.h:9529
StatusCode
Definition httplib.h:715
@ Conflict_409
Definition httplib.h:755
@ IMUsed_226
Definition httplib.h:732
@ SwitchingProtocol_101
Definition httplib.h:718
@ MisdirectedRequest_421
Definition httplib.h:765
@ FailedDependency_424
Definition httplib.h:768
@ Found_302
Definition httplib.h:737
@ MultipleChoices_300
Definition httplib.h:735
@ UnavailableForLegalReasons_451
Definition httplib.h:774
@ LoopDetected_508
Definition httplib.h:785
@ ServiceUnavailable_503
Definition httplib.h:780
@ EarlyHints_103
Definition httplib.h:720
@ ProxyAuthenticationRequired_407
Definition httplib.h:753
@ Processing_102
Definition httplib.h:719
@ Locked_423
Definition httplib.h:767
@ PaymentRequired_402
Definition httplib.h:748
@ NonAuthoritativeInformation_203
Definition httplib.h:726
@ UriTooLong_414
Definition httplib.h:760
@ Accepted_202
Definition httplib.h:725
@ ResetContent_205
Definition httplib.h:728
@ Forbidden_403
Definition httplib.h:749
@ Gone_410
Definition httplib.h:756
@ UnsupportedMediaType_415
Definition httplib.h:761
@ NotExtended_510
Definition httplib.h:786
@ NotModified_304
Definition httplib.h:739
@ VariantAlsoNegotiates_506
Definition httplib.h:783
@ unused_306
Definition httplib.h:741
@ TooManyRequests_429
Definition httplib.h:772
@ RequestTimeout_408
Definition httplib.h:754
@ OK_200
Definition httplib.h:723
@ NetworkAuthenticationRequired_511
Definition httplib.h:787
@ AlreadyReported_208
Definition httplib.h:731
@ MultiStatus_207
Definition httplib.h:730
@ ImATeapot_418
Definition httplib.h:764
@ NotAcceptable_406
Definition httplib.h:752
@ InternalServerError_500
Definition httplib.h:777
@ ExpectationFailed_417
Definition httplib.h:763
@ MethodNotAllowed_405
Definition httplib.h:751
@ NotFound_404
Definition httplib.h:750
@ TemporaryRedirect_307
Definition httplib.h:742
@ PermanentRedirect_308
Definition httplib.h:743
@ SeeOther_303
Definition httplib.h:738
@ Unauthorized_401
Definition httplib.h:747
@ PreconditionFailed_412
Definition httplib.h:758
@ Created_201
Definition httplib.h:724
@ NotImplemented_501
Definition httplib.h:778
@ UseProxy_305
Definition httplib.h:740
@ NoContent_204
Definition httplib.h:727
@ InsufficientStorage_507
Definition httplib.h:784
@ TooEarly_425
Definition httplib.h:769
@ BadGateway_502
Definition httplib.h:779
@ MovedPermanently_301
Definition httplib.h:736
@ PreconditionRequired_428
Definition httplib.h:771
@ PartialContent_206
Definition httplib.h:729
@ LengthRequired_411
Definition httplib.h:757
@ PayloadTooLarge_413
Definition httplib.h:759
@ RangeNotSatisfiable_416
Definition httplib.h:762
@ UnprocessableContent_422
Definition httplib.h:766
@ RequestHeaderFieldsTooLarge_431
Definition httplib.h:773
@ Continue_100
Definition httplib.h:717
@ BadRequest_400
Definition httplib.h:746
@ UpgradeRequired_426
Definition httplib.h:770
@ GatewayTimeout_504
Definition httplib.h:781
@ HttpVersionNotSupported_505
Definition httplib.h:782
std::string encode_uri(const std::string &value)
Definition httplib.h:9341
std::function< bool(size_t current, size_t total)> UploadProgress
Definition httplib.h:798
void default_socket_options(socket_t sock)
Definition httplib.h:9136
std::string append_query_params(const std::string &path, const Params &params)
Definition httplib.h:9578
std::pair< ssize_t, ssize_t > Range
Definition httplib.h:1122
std::multimap< std::string, FormData > FormFiles
Definition httplib.h:959
ClientConnection & operator=(ClientConnection &&other) noexcept
Definition httplib.h:1925
ClientConnection(ClientConnection &&other) noexcept
Definition httplib.h:1912
~ClientConnection()
Definition httplib.h:14657
ClientConnection & operator=(const ClientConnection &)=delete
socket_t sock
Definition httplib.h:1901
ClientConnection(const ClientConnection &)=delete
bool is_open() const
Definition httplib.h:1903
Definition httplib.h:2190
bool is_open() const
Definition httplib.h:2196
socket_t sock
Definition httplib.h:2191
std::chrono::time_point< std::chrono::steady_clock > start_time_
Definition httplib.h:2194
Definition httplib.h:1985
bool trailers_parsed_
Definition httplib.h:2005
friend class ClientImpl
Definition httplib.h:2008
bool has_read_error() const
Definition httplib.h:2003
Error error
Definition httplib.h:1987
StreamHandle & operator=(StreamHandle &&)=default
ssize_t read(char *buf, size_t len)
Definition httplib.h:12677
StreamHandle(const StreamHandle &)=delete
bool is_valid() const
Definition httplib.h:1996
StreamHandle & operator=(const StreamHandle &)=delete
Error get_read_error() const
Definition httplib.h:2002
StreamHandle(StreamHandle &&)=default
void parse_trailers_if_needed()
Definition httplib.h:12750
std::unique_ptr< Response > response
Definition httplib.h:1986
Definition httplib.h:1027
std::string name
Definition httplib.h:1028
std::string filename
Definition httplib.h:1030
std::string content_type
Definition httplib.h:1031
ContentProviderWithoutLength provider
Definition httplib.h:1029
Definition httplib.h:944
std::string content
Definition httplib.h:946
std::string filename
Definition httplib.h:947
std::string content_type
Definition httplib.h:948
Headers headers
Definition httplib.h:949
std::string name
Definition httplib.h:945
Definition httplib.h:952
std::string content
Definition httplib.h:954
Headers headers
Definition httplib.h:955
std::string name
Definition httplib.h:953
Definition httplib.h:961
std::string get_field(const std::string &key, size_t id=0) const
Definition httplib.h:9688
bool has_file(const std::string &key) const
Definition httplib.h:9735
FormData get_file(const std::string &key, size_t id=0) const
Definition httplib.h:9716
bool has_field(const std::string &key) const
Definition httplib.h:9707
size_t get_file_count(const std::string &key) const
Definition httplib.h:9739
std::vector< FormData > get_files(const std::string &key) const
Definition httplib.h:9726
FormFiles files
Definition httplib.h:963
FormFields fields
Definition httplib.h:962
std::vector< std::string > get_fields(const std::string &key) const
Definition httplib.h:9698
size_t get_field_count(const std::string &key) const
Definition httplib.h:9711
Definition httplib.h:1224
MultipartFormData form
Definition httplib.h:1241
bool has_param(const std::string &key) const
Definition httplib.h:9664
bool has_header(const std::string &key) const
Definition httplib.h:9624
DownloadProgress download_progress
Definition httplib.h:1251
size_t get_param_value_count(const std::string &key) const
Definition httplib.h:9677
std::vector< std::string > accept_content_types
Definition httplib.h:1248
size_t get_trailer_value_count(const std::string &key) const
Definition httplib.h:9659
ContentReceiverWithProgress content_receiver
Definition httplib.h:1250
std::string local_addr
Definition httplib.h:1235
std::unordered_map< std::string, std::string > path_params
Definition httplib.h:1244
UploadProgress upload_progress
Definition httplib.h:1252
Headers trailers
Definition httplib.h:1230
std::string remote_addr
Definition httplib.h:1233
std::string get_header_value(const std::string &key, const char *def="", size_t id=0) const
Definition httplib.h:9628
int remote_port
Definition httplib.h:1234
Params params
Definition httplib.h:1228
std::string target
Definition httplib.h:1240
size_t get_header_value_u64(const std::string &key, size_t def=0, size_t id=0) const
Definition httplib.h:9619
std::string get_param_value(const std::string &key, size_t id=0) const
Definition httplib.h:9668
size_t authorization_count_
Definition httplib.h:1277
bool is_multipart_form_data() const
Definition httplib.h:9682
Match matches
Definition httplib.h:1243
std::string version
Definition httplib.h:1239
bool has_trailer(const std::string &key) const
Definition httplib.h:9646
std::chrono::time_point< std::chrono::steady_clock > start_time_
Definition httplib.h:1278
ContentProvider content_provider_
Definition httplib.h:1275
std::string get_trailer_value(const std::string &key, size_t id=0) const
Definition httplib.h:9650
size_t content_length_
Definition httplib.h:1274
Ranges ranges
Definition httplib.h:1242
std::string path
Definition httplib.h:1226
bool is_chunked_content_provider_
Definition httplib.h:1276
std::function< bool()> is_connection_closed
Definition httplib.h:1245
std::string body
Definition httplib.h:1231
size_t redirect_count_
Definition httplib.h:1273
std::string matched_route
Definition httplib.h:1227
size_t get_header_value_count(const std::string &key) const
Definition httplib.h:9633
ResponseHandler response_handler
Definition httplib.h:1249
int local_port
Definition httplib.h:1236
std::string method
Definition httplib.h:1225
void set_header(const std::string &key, const std::string &val)
Definition httplib.h:9638
Headers headers
Definition httplib.h:1229
Definition httplib.h:1288
ContentProvider content_provider_
Definition httplib.h:1347
bool is_chunked_content_provider_
Definition httplib.h:1349
Headers trailers
Definition httplib.h:1293
void set_redirect(const std::string &url, int status=StatusCode::Found_302)
Definition httplib.h:9790
Response & operator=(Response &&)=default
std::string file_content_path_
Definition httplib.h:1351
size_t get_header_value_u64(const std::string &key, size_t def=0, size_t id=0) const
Definition httplib.h:9745
Response()=default
size_t content_length_
Definition httplib.h:1346
void set_content_provider(size_t length, const std::string &content_type, ContentProvider provider, ContentProviderResourceReleaser resource_releaser=nullptr)
Definition httplib.h:9824
std::string version
Definition httplib.h:1289
Headers headers
Definition httplib.h:1292
void set_content(const char *s, size_t n, const std::string &content_type)
Definition httplib.h:9801
bool has_header(const std::string &key) const
Definition httplib.h:9750
bool content_provider_success_
Definition httplib.h:1350
ContentProviderResourceReleaser content_provider_resource_releaser_
Definition httplib.h:1348
std::string get_header_value(const std::string &key, const char *def="", size_t id=0) const
Definition httplib.h:9754
void set_chunked_content_provider(const std::string &content_type, ContentProviderWithoutLength provider, ContentProviderResourceReleaser resource_releaser=nullptr)
Definition httplib.h:9844
int status
Definition httplib.h:1290
std::string get_trailer_value(const std::string &key, size_t id=0) const
Definition httplib.h:9776
std::string reason
Definition httplib.h:1291
size_t get_trailer_value_count(const std::string &key) const
Definition httplib.h:9785
std::map< std::string, any > user_data
Definition httplib.h:1299
std::string body
Definition httplib.h:1294
void set_file_content(const std::string &path, const std::string &content_type)
Definition httplib.h:9854
bool has_trailer(const std::string &key) const
Definition httplib.h:9772
Response & operator=(const Response &)=default
Response(const Response &)=default
~Response()
Definition httplib.h:1339
std::string file_content_content_type_
Definition httplib.h:1352
Response(Response &&)=default
void set_header(const std::string &key, const std::string &val)
Definition httplib.h:9765
size_t get_header_value_count(const std::string &key) const
Definition httplib.h:9760
std::string location
Definition httplib.h:1295
Definition httplib.h:978
std::string filename
Definition httplib.h:981
std::string content_type
Definition httplib.h:982
std::string content
Definition httplib.h:980
std::string name
Definition httplib.h:979
Definition httplib.h:1946
size_t bytes_read
Definition httplib.h:1951
bool has_content_length
Definition httplib.h:1948
std::unique_ptr< ChunkedDecoder > chunked_decoder
Definition httplib.h:1954
bool has_error() const
Definition httplib.h:1958
size_t content_length
Definition httplib.h:1949
Stream * stream
Definition httplib.h:1947
bool chunked
Definition httplib.h:1952
size_t payload_max_length
Definition httplib.h:1950
ssize_t read(char *buf, size_t len)
Definition httplib.h:9897
Error last_error
Definition httplib.h:1955
bool eof
Definition httplib.h:1953
Definition httplib.h:3227
char line_buf[64]
Definition httplib.h:3231
ssize_t read_payload(char *buf, size_t len, size_t &out_chunk_offset, size_t &out_chunk_total)
Definition httplib.h:12773
size_t last_chunk_offset
Definition httplib.h:3233
size_t chunk_remaining
Definition httplib.h:3229
bool parse_trailers_into(Headers &dest, const Headers &src_headers)
Definition httplib.h:12820
bool finished
Definition httplib.h:3230
size_t last_chunk_total
Definition httplib.h:3232
Stream & strm
Definition httplib.h:3228
ChunkedDecoder(Stream &s)
Definition httplib.h:12771
Definition httplib.h:2985
time_t mtime() const
Definition httplib.h:4947
FileStat(const std::string &path)
Definition httplib.h:4932
size_t size() const
Definition httplib.h:4952
bool is_file() const
Definition httplib.h:4940
bool is_dir() const
Definition httplib.h:4943
Definition httplib.h:8215
size_t size
Definition httplib.h:8217
const char * data
Definition httplib.h:8216
Definition httplib.h:841
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:847
any_type_id type_id() const noexcept override
Definition httplib.h:853
any_value(U &&v)
Definition httplib.h:849
std::unique_ptr< any_storage > clone() const override
Definition httplib.h:850
T value
Definition httplib.h:848
bool operator()(const std::string &a, const std::string &b) const
Definition httplib.h:574
Definition httplib.h:579
size_t operator()(const std::string &key) const
Definition httplib.h:580
size_t hash_core(const char *s, size_t l, size_t h) const
Definition httplib.h:584
Definition httplib.h:631
std::errc ec
Definition httplib.h:633
const char * ptr
Definition httplib.h:632
Definition httplib.h:604
scope_exit(std::function< void(void)> &&f)
Definition httplib.h:605
scope_exit(scope_exit &&rhs) noexcept
Definition httplib.h:608
void release()
Definition httplib.h:618
~scope_exit()
Definition httplib.h:614
Definition httplib.h:3636
std::string event
Definition httplib.h:3637
void clear()
Definition httplib.h:3968
SSEMessage()
Definition httplib.h:3966
std::string data
Definition httplib.h:3638
std::string id
Definition httplib.h:3639